From cef282ac4c6e15d61194674f5172f664046ca340 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Wed, 3 Dec 2025 12:59:19 +0200 Subject: [PATCH 01/14] [WAL-1471] Add support for sponsored transactions Fix web-sdk compatibility --- examples/wallet-api/package.json | 2 +- .../browser-wallet-api-helpers/package.json | 2 +- .../src/wallet-api-types.ts | 42 +++++----- packages/browser-wallet/package.json | 2 +- .../src/background/credential-deployment.ts | 14 ++-- .../AccountTransactionFlow.tsx | 6 +- .../pages/Account/ConfirmGenericTransfer.tsx | 4 +- .../ConfirmTransfer/ConfirmTransfer.tsx | 16 ++-- .../popup/pages/Account/Earn/Baking/utils.ts | 12 +-- .../src/popup/pages/Account/Earn/Earn.tsx | 6 +- .../Account/SendCcd/ConfirmTokenTransfer.tsx | 6 +- .../Validator/TransactionFlow.tsx | 6 +- .../pages/EarningRewards/Validator/util.ts | 2 +- .../SubmittedTransaction.tsx | 21 ++--- .../DisplayTransactionPayload.tsx | 12 +-- .../TransactionReceipt/TransactionReceipt.tsx | 23 +++--- .../displayPayload/DisplayInitContract.tsx | 4 +- .../displayPayload/DisplayUpdateContract.tsx | 4 +- .../src/popup/shared/utils/baking-helpers.ts | 6 +- .../popup/shared/utils/transaction-helpers.ts | 38 ++++----- .../shared/utils/transaction-history-types.ts | 18 +++-- .../src/shared/utils/payload-helpers.ts | 34 ++++---- .../src/shared/utils/token-helpers.ts | 4 +- .../utils/verifiable-credential-helpers.ts | 20 ++--- .../src/wallet-api/compatibility.ts | 37 +++++---- yarn.lock | 77 ++++++++++++++----- 26 files changed, 241 insertions(+), 177 deletions(-) diff --git a/examples/wallet-api/package.json b/examples/wallet-api/package.json index 06e4ef1ba..204e7c469 100644 --- a/examples/wallet-api/package.json +++ b/examples/wallet-api/package.json @@ -12,7 +12,7 @@ "start:sign-token-update": "FILE=sign-token-update.html yarn start" }, "dependencies": { - "@concordium/web-sdk": "10.0.2" + "@concordium/web-sdk": "12.0.0-devnet-p10.0" }, "devDependencies": { "live-server": "^1.2.2" diff --git a/packages/browser-wallet-api-helpers/package.json b/packages/browser-wallet-api-helpers/package.json index e28de5291..3263190a0 100644 --- a/packages/browser-wallet-api-helpers/package.json +++ b/packages/browser-wallet-api-helpers/package.json @@ -40,7 +40,7 @@ "webpack-cli": "^4.9.2" }, "peerDependencies": { - "@concordium/web-sdk": "10.0.2", + "@concordium/web-sdk": "12.0.0-devnet-p10.0", "@protobuf-ts/runtime-rpc": "^2.9.1" }, "scripts": { diff --git a/packages/browser-wallet-api-helpers/src/wallet-api-types.ts b/packages/browser-wallet-api-helpers/src/wallet-api-types.ts index 7fb636320..eba8f2998 100644 --- a/packages/browser-wallet-api-helpers/src/wallet-api-types.ts +++ b/packages/browser-wallet-api-helpers/src/wallet-api-types.ts @@ -1,30 +1,30 @@ import type { - AccountTransactionPayload, + AccountAddress, + AccountTransactionInput, AccountTransactionSignature, AccountTransactionType, - InitContractPayload, - SchemaVersion, - UpdateContractPayload, - IdStatement, - IdProofOutput, - CredentialStatements, - VerifiablePresentation, - CredentialSubject, - HexString, - AccountAddress, Base58String, Base64String, - ContractAddress, - UpdateCredentialsPayload, - RegisterDataPayload, - SimpleTransferPayload, - SimpleTransferWithMemoPayload, - DeployModulePayload, ConfigureBakerPayload, ConfigureDelegationPayload, + ContractAddress, ContractName, + CredentialStatements, + CredentialSubject, + DeployModulePayload, EntrypointName, + HexString, + IdProofOutput, + IdStatement, + InitContractInput, + RegisterDataPayload, + SchemaVersion, + SimpleTransferPayload, + SimpleTransferWithMemoPayload, TokenUpdatePayload, + UpdateContractInput, + UpdateCredentialsInput, + VerifiablePresentation, } from '@concordium/web-sdk'; import type { RpcTransport } from '@protobuf-ts/runtime-rpc'; import { LaxNumberEnumValue, LaxStringEnumValue } from './util'; @@ -61,11 +61,11 @@ export interface CredentialProof { verificationMethod: string; } -export type SendTransactionUpdateContractPayload = Omit; -export type SendTransactionInitContractPayload = Omit; +export type SendTransactionUpdateContractPayload = Omit; +export type SendTransactionInitContractPayload = Omit; export type SendTransactionPayload = - | Exclude + | Exclude | SendTransactionUpdateContractPayload | SendTransactionInitContractPayload; @@ -168,7 +168,7 @@ interface MainWalletApi { sendTransaction( accountAddress: AccountAddressSource, type: LaxNumberEnumValue, - payload: UpdateCredentialsPayload + payload: UpdateCredentialsInput ): Promise; /** * Sends a transaction to the Concordium Wallet and awaits the users action. Note that a header is not sent, and will be constructed by the wallet itself. diff --git a/packages/browser-wallet/package.json b/packages/browser-wallet/package.json index 1128c6a40..e27ba6bba 100644 --- a/packages/browser-wallet/package.json +++ b/packages/browser-wallet/package.json @@ -22,7 +22,7 @@ "dependencies": { "@concordium/browser-wallet-api-helpers": "workspace:^", "@concordium/common-sdk": "^9.5.3", - "@concordium/web-sdk": "10.0.2", + "@concordium/web-sdk": "12.0.0-devnet-p10.0", "@floating-ui/react": "^0.27.2", "@noble/ed25519": "^1.7.0", "@protobuf-ts/grpcweb-transport": "^2.9.1", diff --git a/packages/browser-wallet/src/background/credential-deployment.ts b/packages/browser-wallet/src/background/credential-deployment.ts index 9bb9e518e..96c6cfbed 100644 --- a/packages/browser-wallet/src/background/credential-deployment.ts +++ b/packages/browser-wallet/src/background/credential-deployment.ts @@ -1,16 +1,16 @@ import { ExtensionMessageHandler } from '@messaging'; import { - CredentialInput, - getAccountAddress, ConcordiumGRPCWebClient, - createCredentialTransaction, - signCredentialTransaction, ConcordiumHdWallet, - TransactionExpiry, - getCredentialDeploymentTransactionHash, + createCredentialPayload, + CredentialInput, CredentialRegistrationId, + getAccountAddress, + getCredentialDeploymentTransactionHash, serializeCredentialDeploymentPayload, + signCredentialTransaction, + TransactionExpiry, } from '@concordium/web-sdk'; import { GRPCTIMEOUT } from '@shared/constants/networkConfiguration'; import { DEFAULT_TRANSACTION_EXPIRY } from '@shared/constants/time'; @@ -32,7 +32,7 @@ async function createAndSendCredential(credIn: CredentialInput): Promise> /** * Function to convert flow values into an account transaction. */ - convert(values: F): AccountTransactionPayload; + convert(values: F): AccountTransactionInput; /** * Function that is triggered if an error is thrown in the done handler. */ @@ -56,7 +56,7 @@ export default function AccountTransactionFlow const handleDone = useCallback( (values: F) => { - let payload: AccountTransactionPayload; + let payload: AccountTransactionInput; try { payload = convert(values); nav(pathname, { replace: true, state: values }); // Override current router entry with stateful version diff --git a/packages/browser-wallet/src/popup/pages/Account/ConfirmGenericTransfer.tsx b/packages/browser-wallet/src/popup/pages/Account/ConfirmGenericTransfer.tsx index 55e3abdc1..f3a9da8c8 100644 --- a/packages/browser-wallet/src/popup/pages/Account/ConfirmGenericTransfer.tsx +++ b/packages/browser-wallet/src/popup/pages/Account/ConfirmGenericTransfer.tsx @@ -1,6 +1,6 @@ import React, { useContext, useMemo } from 'react'; import { - AccountTransactionPayload, + AccountTransactionInput, AccountTransactionType, convertEnergyToMicroCcd, getEnergyCost, @@ -12,7 +12,7 @@ import ConfirmTransfer from './ConfirmTransfer'; import { accountPageContext } from './utils'; export type ConfirmGenericTransferState = { - payload: AccountTransactionPayload; + payload: AccountTransactionInput; type: AccountTransactionType; }; diff --git a/packages/browser-wallet/src/popup/pages/Account/ConfirmTransfer/ConfirmTransfer.tsx b/packages/browser-wallet/src/popup/pages/Account/ConfirmTransfer/ConfirmTransfer.tsx index be111f0e5..3c030c859 100644 --- a/packages/browser-wallet/src/popup/pages/Account/ConfirmTransfer/ConfirmTransfer.tsx +++ b/packages/browser-wallet/src/popup/pages/Account/ConfirmTransfer/ConfirmTransfer.tsx @@ -1,17 +1,17 @@ /* eslint-disable react/destructuring-assignment */ -import React, { useEffect, useState, useMemo } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useSetAtom, useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { selectedAccountAtom } from '@popup/store/account'; import { AccountAddress, - AccountTransactionPayload, + AccountTransactionInput, AccountTransactionType, - UpdateContractPayload, + UpdateContractInput, } from '@concordium/web-sdk'; import { - getDefaultExpiry, createPendingTransactionFromAccountTransaction, + getDefaultExpiry, sendTransaction, } from '@popup/shared/utils/transaction-helpers'; import { grpcClientAtom } from '@popup/store/settings'; @@ -21,8 +21,8 @@ import Button from '@popup/shared/Button'; import { useNavigate } from 'react-router-dom'; import { absoluteRoutes } from '@popup/constants/routes'; import GenericTransactionReceipt, { - TokenTransferReceipt, GenericTransactionReceiptProps, + TokenTransferReceipt, TokenTransferReceiptProps, } from '@popup/shared/TransactionReceipt'; import { useUpdateAtom } from 'jotai/utils'; @@ -42,14 +42,14 @@ type BaseProps = { type ConfirmTokenTransferProps = BaseProps & { showAsTokenTransfer: true; transactionType: AccountTransactionType.Update; - payload: UpdateContractPayload; + payload: UpdateContractInput; parameters: SmartContractParameters; metadata: TokenMetadata; }; type ConfirmGenericTransferProps = BaseProps & { showAsTokenTransfer?: false; - payload: AccountTransactionPayload; + payload: AccountTransactionInput; transactionType: AccountTransactionType; parameters?: SmartContractParameters; }; diff --git a/packages/browser-wallet/src/popup/pages/Account/Earn/Baking/utils.ts b/packages/browser-wallet/src/popup/pages/Account/Earn/Baking/utils.ts index 1a1bdeeca..187f43acb 100644 --- a/packages/browser-wallet/src/popup/pages/Account/Earn/Baking/utils.ts +++ b/packages/browser-wallet/src/popup/pages/Account/Earn/Baking/utils.ts @@ -1,14 +1,14 @@ import { + AccountBakerDetailsV1, AccountInfo, + AccountInfoBaker, + AccountInfoType, + BakerKeysWithProofs, CcdAmount, CommissionRates, - OpenStatus, ConfigureBakerPayload, + OpenStatus, OpenStatusText, - BakerKeysWithProofs, - AccountInfoType, - AccountInfoBaker, - AccountBakerDetailsV1, } from '@concordium/web-sdk'; import { decimalToRewardFraction, fractionToPercentage } from '@popup/shared/utils/baking-helpers'; import { getConfigureBakerEnergyCost } from '@shared/utils/energy-helpers'; @@ -26,7 +26,7 @@ export type ConfigureBakerFlowState = { keys: BakerKeysWithProofs | null; }; -function openStatusFromText(status: OpenStatusText): OpenStatus { +function openStatusFromText(status: OpenStatusText | null): OpenStatus { switch (status) { case OpenStatusText.ClosedForAll: return OpenStatus.ClosedForAll; diff --git a/packages/browser-wallet/src/popup/pages/Account/Earn/Earn.tsx b/packages/browser-wallet/src/popup/pages/Account/Earn/Earn.tsx index 525718406..7ba983138 100644 --- a/packages/browser-wallet/src/popup/pages/Account/Earn/Earn.tsx +++ b/packages/browser-wallet/src/popup/pages/Account/Earn/Earn.tsx @@ -24,7 +24,7 @@ import { useBlockChainParametersAboveV0 } from '@popup/shared/BlockChainParamete import Baking from './Baking'; import Delegate from './Delegate'; import { accountPageContext } from '../utils'; -import { filterType, EarnPageContext, earnPageContext } from './utils'; +import { EarnPageContext, earnPageContext, filterType } from './utils'; import { accountRoutes } from '../routes'; const routes = { @@ -73,7 +73,9 @@ function Earn({ chainParameters }: EarnProps) { ); } -function isNotRewardStatusV0(rewardStatus?: RewardStatus): rewardStatus is Exclude { +function isNotRewardStatusV0( + rewardStatus?: RewardStatus | null +): rewardStatus is Exclude { return rewardStatus ? rewardStatus.version !== 0 : false; } diff --git a/packages/browser-wallet/src/popup/pages/Account/SendCcd/ConfirmTokenTransfer.tsx b/packages/browser-wallet/src/popup/pages/Account/SendCcd/ConfirmTokenTransfer.tsx index bdf639f29..74029e75a 100644 --- a/packages/browser-wallet/src/popup/pages/Account/SendCcd/ConfirmTokenTransfer.tsx +++ b/packages/browser-wallet/src/popup/pages/Account/SendCcd/ConfirmTokenTransfer.tsx @@ -1,8 +1,8 @@ import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useSetAtom, useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { selectedAccountAtom } from '@popup/store/account'; -import { AccountTransactionType, UpdateContractPayload } from '@concordium/web-sdk'; +import { AccountTransactionType, UpdateContractInput } from '@concordium/web-sdk'; import { grpcClientAtom } from '@popup/store/settings'; import { addToastAtom } from '@popup/state'; import { useLocation } from 'react-router-dom'; @@ -30,7 +30,7 @@ export default function ConfirmTokenTransfer({ setDetailsExpanded, cost }: Props [] ); - const payload: UpdateContractPayload | undefined = useMemo(() => { + const payload: UpdateContractInput | undefined = useMemo(() => { if (!parameters || !contractName) { return undefined; } diff --git a/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/TransactionFlow.tsx b/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/TransactionFlow.tsx index 345984b7b..cbfb2cc92 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/TransactionFlow.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/TransactionFlow.tsx @@ -14,7 +14,7 @@ import { useSelectedAccountInfo } from '@popup/shared/AccountInfoListenerContext import { formatCcdAmount } from '@popup/popupX/shared/utils/helpers'; import Text from '@popup/popupX/shared/Text'; -import { ValidatorForm, ValidatorFormExisting, configureValidatorFromForm } from './util'; +import { configureValidatorFromForm, ValidatorForm, ValidatorFormExisting } from './util'; import ValidatorStake from './Stake'; import { type ValidationResultLocationState } from './Result'; import OpenPool from './OpenPool'; @@ -136,6 +136,10 @@ function withChangeValidation(Flow: ComponentType) { accountBaker: { stakedAmount, restakeEarnings, bakerPoolInfo }, } = accountInfo; + if (bakerPoolInfo.openStatus === null) { + return null; + } + const existing: ValidatorFormExisting = { stake: { amount: formatCcdAmount(stakedAmount), diff --git a/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/util.ts b/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/util.ts index 9a923840b..5bb9ba9e4 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/util.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/EarningRewards/Validator/util.ts @@ -17,7 +17,7 @@ export function showValidatorAmount(amount: CcdAmount.Type): string { return `${formatCcdAmount(amount)} CCD`; } -export function showValidatorOpenStatus(status: OpenStatusText | OpenStatus): string { +export function showValidatorOpenStatus(status: OpenStatusText | OpenStatus | null): string { switch (status) { case OpenStatus.OpenForAll: case OpenStatusText.OpenForAll: diff --git a/packages/browser-wallet/src/popup/popupX/pages/SubmittedTransaction/SubmittedTransaction.tsx b/packages/browser-wallet/src/popup/popupX/pages/SubmittedTransaction/SubmittedTransaction.tsx index d2d9e67aa..ad9b259ab 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/SubmittedTransaction/SubmittedTransaction.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/SubmittedTransaction/SubmittedTransaction.tsx @@ -10,21 +10,21 @@ import { absoluteRoutes, transactionDetailsRoute } from '@popup/popupX/constants import Card from '@popup/popupX/shared/Card'; import { useAsyncMemo } from 'wallet-common-helpers'; import { + AccountTransactionPayload, AccountTransactionSummary, + AccountTransactionType, + BaseAccountTransactionSummary, + CcdAmount, + ConfigureBakerPayload, + ConfigureDelegationPayload, + FailedTransactionSummary, HexString, - TransactionHash, - TransactionSummaryType, isRejectTransaction, isSuccessTransaction, - FailedTransactionSummary, - BaseAccountTransactionSummary, - AccountTransactionPayload, - AccountTransactionType, SimpleTransferPayload, SimpleTransferWithMemoPayload, - ConfigureBakerPayload, - ConfigureDelegationPayload, - CcdAmount, + TransactionHash, + TransactionSummaryType, } from '@concordium/web-sdk'; import { useAtomValue } from 'jotai'; import { grpcClientAtom } from '@popup/store/settings'; @@ -284,6 +284,9 @@ export default function SubmittedTransaction() { TX_TIMEOUT ); + if (!outcome.summary) { + throw Error('Unexpected transaction type'); + } if (isRejectTransaction(outcome.summary)) { return { type: 'failure', summary: outcome.summary }; } diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx index 8064ce478..17f7697c7 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx @@ -4,12 +4,12 @@ import { AccountTransactionType, CcdAmount, DeployModulePayload, - InitContractPayload, + InitContractInput, RegisterDataPayload, sha256, SimpleTransferPayload, - UpdateContractPayload, TokenUpdatePayload, + UpdateContractInput, } from '@concordium/web-sdk'; import { Cbor, CborMemo, TokenOperationType } from '@concordium/web-sdk/plt'; import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; @@ -45,7 +45,7 @@ function DisplaySimpleTransfer({ payload }: { payload: SimpleTransferPayload }) /** * Displays an overview of a update contract transaction. */ -function DisplayUpdateContract({ payload }: { payload: Omit }) { +function DisplayUpdateContract({ payload }: { payload: Omit }) { const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); return ( <> @@ -66,7 +66,7 @@ function DisplayUpdateContract({ payload }: { payload: Omit }) { +function DisplayInitContract({ payload }: { payload: Omit }) { const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); return ( @@ -216,9 +216,9 @@ export default function DisplayTransactionPayload({ case AccountTransactionType.Transfer: return ; case AccountTransactionType.Update: - return ; + return ; case AccountTransactionType.InitContract: - return ; + return ; case AccountTransactionType.RegisterData: return ; case AccountTransactionType.DeployModule: diff --git a/packages/browser-wallet/src/popup/shared/TransactionReceipt/TransactionReceipt.tsx b/packages/browser-wallet/src/popup/shared/TransactionReceipt/TransactionReceipt.tsx index 6c3a0b30b..996442f4f 100644 --- a/packages/browser-wallet/src/popup/shared/TransactionReceipt/TransactionReceipt.tsx +++ b/packages/browser-wallet/src/popup/shared/TransactionReceipt/TransactionReceipt.tsx @@ -3,22 +3,23 @@ import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; import React, { useState } from 'react'; import { - AccountTransactionType, AccountTransaction, - AccountTransactionPayload, - SimpleTransferPayload, - UpdateContractPayload, - InitContractPayload, - RegisterDataPayload, - ConfigureDelegationPayload, + AccountTransactionInput, + AccountTransactionType, ConfigureBakerPayload, + ConfigureDelegationPayload, DeployModulePayload, + InitContractInput, + RegisterDataPayload, + SimpleTransferPayload, + UpdateContractInput, + UpdateContractPayload, } from '@concordium/web-sdk'; import { Cis2TransferParameters } from '@shared/utils/types'; import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; import { TokenMetadata } from '@shared/storage/types'; import { getMetadataDecimals } from '@shared/utils/token-helpers'; -import { chunkString, DisplayAddress, AddressDisplayFormat } from 'wallet-common-helpers'; +import { AddressDisplayFormat, chunkString, DisplayAddress } from 'wallet-common-helpers'; import DisplayCost from './DisplayCost'; import { getTransactionTypeName } from '../utils/transaction-helpers'; import DisplayUpdateContract from './displayPayload/DisplayUpdateContract'; @@ -35,7 +36,7 @@ import DisplayDeployModule from './displayPayload/DisplayDeployModule'; export type GenericTransactionReceiptProps = { className?: string; transactionType: AccountTransactionType; - payload: AccountTransactionPayload; + payload: AccountTransactionInput; parameters?: SmartContractParameters; sender: string; cost?: bigint; @@ -63,9 +64,9 @@ function displayPayload({ payload, type }: Omit, p case AccountTransactionType.Transfer: return ; case AccountTransactionType.Update: - return ; + return ; case AccountTransactionType.InitContract: - return ; + return ; case AccountTransactionType.RegisterData: return ; case AccountTransactionType.ConfigureDelegation: diff --git a/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayInitContract.tsx b/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayInitContract.tsx index e478d88e9..a867510b3 100644 --- a/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayInitContract.tsx +++ b/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayInitContract.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { InitContractPayload } from '@concordium/web-sdk'; +import { InitContractInput } from '@concordium/web-sdk'; import { displayAsCcd } from 'wallet-common-helpers'; import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; import DisplayParameters from '../DisplayParameters'; interface Props { - payload: Omit; + payload: Omit; parameters?: SmartContractParameters; } diff --git a/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayUpdateContract.tsx b/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayUpdateContract.tsx index 2cd81417e..f156dcbbe 100644 --- a/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayUpdateContract.tsx +++ b/packages/browser-wallet/src/popup/shared/TransactionReceipt/displayPayload/DisplayUpdateContract.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { UpdateContractPayload } from '@concordium/web-sdk'; +import { UpdateContractInput } from '@concordium/web-sdk'; import { displayAsCcd } from 'wallet-common-helpers'; import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; import DisplayParameters from '../DisplayParameters'; interface Props { - payload: Omit; + payload: Omit; parameters?: SmartContractParameters; } diff --git a/packages/browser-wallet/src/popup/shared/utils/baking-helpers.ts b/packages/browser-wallet/src/popup/shared/utils/baking-helpers.ts index c1c5c7ac1..4e98973cb 100644 --- a/packages/browser-wallet/src/popup/shared/utils/baking-helpers.ts +++ b/packages/browser-wallet/src/popup/shared/utils/baking-helpers.ts @@ -1,8 +1,8 @@ import { - OpenStatus, - OpenStatusText, AccountInfo, GenerateBakerKeysOutput, + OpenStatus, + OpenStatusText, PrivateBakerKeys, PublicBakerKeys, } from '@concordium/web-sdk'; @@ -20,7 +20,7 @@ const fractionResolutionToPercentage = (v: number) => v / percentageModifier; export const fractionToPercentage = (d: number | undefined) => d === undefined ? undefined : Math.floor(d * fractionResolution) / percentageModifier; -export function openStatusToDisplay(status: OpenStatus | OpenStatusText): string { +export function openStatusToDisplay(status: OpenStatus | OpenStatusText | null): string { switch (status) { case OpenStatus.OpenForAll: case OpenStatusText.OpenForAll: diff --git a/packages/browser-wallet/src/popup/shared/utils/transaction-helpers.ts b/packages/browser-wallet/src/popup/shared/utils/transaction-helpers.ts index 1e3b6d85b..8161a533a 100644 --- a/packages/browser-wallet/src/popup/shared/utils/transaction-helpers.ts +++ b/packages/browser-wallet/src/popup/shared/utils/transaction-helpers.ts @@ -1,34 +1,34 @@ import { AccountAddress, + AccountInfo, + AccountInfoType, AccountTransaction, + AccountTransactionInput, AccountTransactionType, - AccountTransactionPayload, + BakerPoolStatusDetails, buildBasicAccountSigner, - getAccountTransactionHash, CcdAmount, - ConcordiumGRPCClient, - signTransaction, - SimpleTransferPayload, - TransactionExpiry, - AccountInfo, ChainParameters, ChainParametersV0, - BakerPoolStatusDetails, - InitContractPayload, - UpdateContractPayload, - SimpleTransferWithMemoPayload, - AccountInfoType, + ConcordiumGRPCClient, convertEnergyToMicroCcd, + getAccountTransactionHash, getEnergyCost, + InitContractInput, + signTransaction, + SimpleTransferPayload, + SimpleTransferWithMemoPayload, + TransactionExpiry, + UpdateContractInput, } from '@concordium/web-sdk'; import { TokenId, TokenModuleState } from '@concordium/web-sdk/plt'; import { - isValidResolutionString, ccdToMicroCcd, displayAsCcd, fractionalToInteger, - isValidCcdString, getPublicAccountAmounts, + isValidCcdString, + isValidResolutionString, } from 'wallet-common-helpers'; import i18n from '@popup/shell/i18n'; @@ -281,12 +281,12 @@ export function useHasPendingTransaction(transactionType: AccountTransactionType * Extract the microCCD amount related for the transaction, excluding the cost. * Note that for many transactions there is no related amount, in which case this returns 0. */ -export function getTransactionAmount(type: AccountTransactionType, payload: AccountTransactionPayload): bigint { +export function getTransactionAmount(type: AccountTransactionType, payload: AccountTransactionInput): bigint { switch (type) { case AccountTransactionType.InitContract: - return (payload as InitContractPayload).amount.microCcdAmount; + return (payload as InitContractInput).amount.microCcdAmount; case AccountTransactionType.Update: - return (payload as UpdateContractPayload).amount.microCcdAmount; + return (payload as UpdateContractInput).amount.microCcdAmount; case AccountTransactionType.Transfer: return (payload as SimpleTransferPayload).amount.microCcdAmount; case AccountTransactionType.TransferWithMemo: @@ -300,7 +300,7 @@ export function useGetTransactionFee() { const cp = useBlockChainParameters(); return useCallback( - (type: AccountTransactionType, payload: AccountTransactionPayload) => { + (type: AccountTransactionType, payload: AccountTransactionInput) => { if (cp === undefined) { return undefined; } @@ -344,7 +344,7 @@ export function useTransactionSubmit(sender: AccountAddress.Type, type: AccountT const addPendingTransaction = useUpdateAtom(addPendingTransactionAtom); return useCallback( - async (payload: AccountTransactionPayload, cost: CcdAmount.Type) => { + async (payload: AccountTransactionInput, cost: CcdAmount.Type) => { const { accountAmount, accountAvailableBalance } = await grpc.getAccountInfo(sender); if (accountAvailableBalance.microCcdAmount < cost.microCcdAmount) { diff --git a/packages/browser-wallet/src/popup/shared/utils/transaction-history-types.ts b/packages/browser-wallet/src/popup/shared/utils/transaction-history-types.ts index a84ae8968..85db5f118 100644 --- a/packages/browser-wallet/src/popup/shared/utils/transaction-history-types.ts +++ b/packages/browser-wallet/src/popup/shared/utils/transaction-history-types.ts @@ -1,16 +1,16 @@ import { AccountTransactionSummary, AccountTransactionType, + BakerEvent, ConcordiumGRPCWebClient, ContractTraceEvent, - BakerEvent, DelegationEvent, FinalizedBlockItem, + getTransactionRejectReason, TransactionEvent, TransactionEventTag, TransactionKindString, TransactionSummaryType, - getTransactionRejectReason, } from '@concordium/web-sdk'; import JSONBig from 'json-bigint'; import { logError } from '@shared/utils/log-helpers'; @@ -184,6 +184,8 @@ function getTransactionAmount(account: string, summary: AccountTransactionSummar return summary.contractInitialized.amount.microCcdAmount; case TransactionKindString.Update: return summary.events.reduce((acc: bigint, e) => { + if (e === null) return acc; + switch (e.tag) { case TransactionEventTag.Updated: return acc - e.amount.microCcdAmount; @@ -237,8 +239,12 @@ function getTransactionMemo(summary: AccountTransactionSummary): string | undefi } function getTransactionEvents(summary: AccountTransactionSummary): string[] | undefined { - const toString = ({ tag, ...rest }: TransactionEvent | ContractTraceEvent | BakerEvent | DelegationEvent) => - `${tag}\n${JSONBig.stringify(rest)}`; + const toString = (event: TransactionEvent | ContractTraceEvent | BakerEvent | DelegationEvent | null) => { + if (event === null) return 'Unknown event'; + + const { tag, ...rest } = event; + return `${tag}\n${JSONBig.stringify(rest)}`; + }; switch (summary.transactionType) { case TransactionKindString.Update: @@ -255,11 +261,13 @@ export async function toBrowserWalletTransaction( account: string, transactionHash: string, grpc: ConcordiumGRPCWebClient -): Promise { +): Promise { const block = await grpc.getBlockInfo(blockHash); const time = BigInt(Math.round(block.blockSlotTime.getTime() / 1000)); const id = -1; + if (summary === null) return null; + if (summary.type !== TransactionSummaryType.AccountTransaction) { return { blockHash: blockHash.toString(), diff --git a/packages/browser-wallet/src/shared/utils/payload-helpers.ts b/packages/browser-wallet/src/shared/utils/payload-helpers.ts index 726c890a0..482d88ed3 100644 --- a/packages/browser-wallet/src/shared/utils/payload-helpers.ts +++ b/packages/browser-wallet/src/shared/utils/payload-helpers.ts @@ -1,34 +1,34 @@ import { + AccountTransactionInput, AccountTransactionType, - SimpleTransferPayload, - UpdateContractPayload, - serializeUpdateContractParameters, - AccountTransactionPayload, - SchemaVersion, - InitContractPayload, - serializeInitContractParameters, - serializeTypeValue, ContractName, EntrypointName, - Parameter, - jsonParse, getAccountTransactionHandler, + InitContractInput, + jsonParse, + Parameter, + SchemaVersion, + serializeInitContractParameters, + serializeTypeValue, + serializeUpdateContractParameters, + SimpleTransferPayload, + UpdateContractInput, } from '@concordium/web-sdk'; import { Buffer } from 'buffer/'; -import { SmartContractParameters, SchemaType, SchemaWithContext } from '@concordium/browser-wallet-api-helpers'; +import { SchemaType, SchemaWithContext, SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; import { serializationTypes } from '@wallet-api/constants'; import * as JSONBig from 'json-bigint'; export type HeadlessTransaction = - | { type: AccountTransactionType.Update; payload: UpdateContractPayload } + | { type: AccountTransactionType.Update; payload: UpdateContractInput } | { type: AccountTransactionType.Transfer; payload: SimpleTransferPayload } - | { type: AccountTransactionType.InitContract; payload: InitContractPayload } + | { type: AccountTransactionType.InitContract; payload: InitContractInput } | { type: Exclude< AccountTransactionType, AccountTransactionType.Transfer | AccountTransactionType.Update | AccountTransactionType.InitContract >; - payload: AccountTransactionPayload; + payload: AccountTransactionInput; }; /** @@ -72,7 +72,7 @@ export function parsePayload( switch (type) { case AccountTransactionType.Update: { - const updatePayload = payload as UpdateContractPayload; + const updatePayload = payload as UpdateContractInput; const [contractName, functionName] = updatePayload.receiveName.value.split('.'); let parameter: Parameter.Type; @@ -100,7 +100,7 @@ export function parsePayload( }; } case AccountTransactionType.InitContract: { - const initPayload = payload as InitContractPayload; + const initPayload = payload as InitContractInput; let parameter: Parameter.Type; if (parameters === undefined || parameters === null || !schema) { parameter = Parameter.empty(); @@ -133,6 +133,6 @@ export function parsePayload( return { type, payload, - }; + } as HeadlessTransaction; } } diff --git a/packages/browser-wallet/src/shared/utils/token-helpers.ts b/packages/browser-wallet/src/shared/utils/token-helpers.ts index ff742e8d6..91cba7761 100644 --- a/packages/browser-wallet/src/shared/utils/token-helpers.ts +++ b/packages/browser-wallet/src/shared/utils/token-helpers.ts @@ -17,7 +17,7 @@ import { ReturnValue, serializeUpdateContractParameters, sha256, - UpdateContractPayload, + UpdateContractInput, } from '@concordium/web-sdk'; import { MetadataUrl, PltMetadataUrl, PltResponse, TokenIdAndMetadata, TokenMetadata } from '@shared/storage/types'; import { CIS2_SCHEMA, CIS2_SCHEMA_CONTRACT_NAME } from '@popup/constants/schema'; @@ -369,7 +369,7 @@ export function getTokenTransferPayload( maxContractExecutionEnergy: bigint, index: bigint, subindex = 0n -): UpdateContractPayload { +): UpdateContractInput { return { amount: CcdAmount.fromMicroCcd(0n), address: ContractAddress.create(index, subindex), diff --git a/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts b/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts index 0570fb7fc..398bf2c16 100644 --- a/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts +++ b/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts @@ -1,15 +1,15 @@ import { CcdAmount, - UpdateContractPayload, ConcordiumGRPCClient, - ContractAddress, - sha256, ConcordiumHdWallet, - ReceiveName, + ContractAddress, Energy, + HexString, Parameter, + ReceiveName, ReturnValue, - HexString, + sha256, + UpdateContractInput, } from '@concordium/web-sdk'; import * as ed from '@noble/ed25519'; import { @@ -199,7 +199,7 @@ export async function buildRevokeTransaction( credentialId: string, maxContractExecutionEnergy: bigint, parameters: RevokeCredentialHolderParam -): Promise { +): Promise { return { address, amount: CcdAmount.fromMicroCcd(0n), @@ -292,7 +292,7 @@ export async function getVerifiableCredentialStatus(client: ConcordiumGRPCClient }); if (result.tag !== 'success') { - throw new Error(result.reason.tag); + throw new Error(result.reason?.tag || 'Transaction failed'); } const { returnValue } = result; @@ -387,7 +387,7 @@ export async function getCredentialRegistryMetadata( }); if (result.tag !== 'success') { - throw new Error(result.reason.tag); + throw new Error(result.reason?.tag || 'Transaction failed'); } const { returnValue } = result; @@ -823,7 +823,7 @@ export async function getVerifiableCredentialEntry( }); if (result.tag !== 'success') { - throw new Error(result.reason.tag); + throw new Error(result.reason?.tag || 'Transaction failed'); } const { returnValue } = result; @@ -1102,7 +1102,7 @@ export async function getCredentialRegistryIssuerKey( }); if (result.tag !== 'success') { - throw new Error(result.reason.tag); + throw new Error(result.reason?.tag || 'Transaction failed'); } const { returnValue } = result; diff --git a/packages/browser-wallet/src/wallet-api/compatibility.ts b/packages/browser-wallet/src/wallet-api/compatibility.ts index 3cd9dca20..b9c5cdceb 100644 --- a/packages/browser-wallet/src/wallet-api/compatibility.ts +++ b/packages/browser-wallet/src/wallet-api/compatibility.ts @@ -1,39 +1,41 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { - SmartContractParameters, - SchemaWithContext, - SchemaType, AccountAddressSource, SchemaSource, - SignMessageObject, - SendTransactionPayload, + SchemaType, + SchemaWithContext, SendTransactionInitContractPayload, + SendTransactionPayload, SendTransactionUpdateContractPayload, + SignMessageObject, + SmartContractParameters, } from '@concordium/browser-wallet-api-helpers'; import { AccountAddress, + AccountTransactionPayload, + AccountTransactionPayloadJSON, AccountTransactionType, CcdAmount, ConfigureBakerPayload, ConfigureDelegationPayload, ContractAddress, ContractName, + DataBlob, DeployModulePayload, Energy, + getAccountTransactionHandler, HexString, - InitContractPayload, + InitContractInput, ModuleReference, ReceiveName, RegisterDataPayload, SchemaVersion, SimpleTransferPayload, SimpleTransferWithMemoPayload, - UpdateCredentialsPayload, - DataBlob, - AccountTransactionPayload, - getAccountTransactionHandler, - AccountTransactionPayloadJSON, TokenUpdatePayload, + UpdateCredentialKeysInput, + UpdateCredentialsInput, + UpdateCredentialsPayload, } from '@concordium/web-sdk/types'; import { empty } from '@concordium/web-sdk/types/Parameter'; import { IdStatement } from '@concordium/web-sdk/id'; @@ -222,7 +224,10 @@ export interface ConfigureDelegationPayloadV0 extends Omit=12.12.47" - checksum: 1e0821876fc55fa1d71a674e65db6227ca398f6ff77735bd44d8d4a554fa97dcddd06e7844c3d7da37350feafd824ec88af04f0ab0e0c2e0bc8f753939935240 + "@grpc/proto-loader": ^0.8.0 + "@js-sdsl/ordered-map": ^4.4.2 + checksum: 3f208c23cc985789934d806045599ad3bfa1b6fd9877151cf9af423b4d708cbb3b0cb1347a147061c6ed36470958fd522fbefbf6ae4ca8246f0860bd647f0e5e languageName: node linkType: hard -"@grpc/grpc-js@npm:^1.9.4": - version: 1.9.5 - resolution: "@grpc/grpc-js@npm:1.9.5" +"@grpc/grpc-js@npm:^1.3.4": + version: 1.9.14 + resolution: "@grpc/grpc-js@npm:1.9.14" dependencies: "@grpc/proto-loader": ^0.7.8 "@types/node": ">=12.12.47" - checksum: 06834554a0935906652b4b9c5c71f08dd9bdcd4a00d65465c569eae770a9856ecabf7711290bf6d935a8127779c1f35d9cc8cf029693493da02864a330c78e25 + checksum: 1e0821876fc55fa1d71a674e65db6227ca398f6ff77735bd44d8d4a554fa97dcddd06e7844c3d7da37350feafd824ec88af04f0ab0e0c2e0bc8f753939935240 languageName: node linkType: hard @@ -2610,6 +2610,20 @@ __metadata: languageName: node linkType: hard +"@grpc/proto-loader@npm:^0.8.0": + version: 0.8.0 + resolution: "@grpc/proto-loader@npm:0.8.0" + dependencies: + lodash.camelcase: ^4.3.0 + long: ^5.0.0 + protobufjs: ^7.5.3 + yargs: ^17.7.2 + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 97570e2e8c29a35b999fa929acad506765e6752d3219764825fecc0ab632492b1ac2e4dd4c0a96cc2e01754d99bfa838dff0729c6608a02f7b027fd19bc1d1e4 + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.11": version: 0.11.11 resolution: "@humanwhocodes/config-array@npm:0.11.11" @@ -3125,6 +3139,13 @@ __metadata: languageName: node linkType: hard +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: a927ae4ff8565ecb75355cc6886a4f8fadbf2af1268143c96c0cce3ba01261d241c3f4ba77f21f3f017a00f91dfe9e0673e95f830255945c80a0e96c6d30508a + languageName: node + linkType: hard + "@mdx-js/react@npm:^1.6.22": version: 1.6.22 resolution: "@mdx-js/react@npm:1.6.22" @@ -13757,6 +13778,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.5.3": + version: 7.5.4 + resolution: "protobufjs@npm:7.5.4" + dependencies: + "@protobufjs/aspromise": ^1.1.2 + "@protobufjs/base64": ^1.1.2 + "@protobufjs/codegen": ^2.0.4 + "@protobufjs/eventemitter": ^1.1.0 + "@protobufjs/fetch": ^1.1.0 + "@protobufjs/float": ^1.0.2 + "@protobufjs/inquire": ^1.1.0 + "@protobufjs/path": ^1.1.2 + "@protobufjs/pool": ^1.1.0 + "@protobufjs/utf8": ^1.1.0 + "@types/node": ">=13.7.0" + long: ^5.0.0 + checksum: 53bf83b9a726b05d43da35bb990dba7536759787dccea9a67b8f31be9df470ba17f1f1b982ca19956cfc7726f3ec7e0e883ca4ad93b5ec753cc025a637fc704f + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -16811,7 +16852,7 @@ __metadata: version: 0.0.0-use.local resolution: "wallet-api@workspace:examples/wallet-api" dependencies: - "@concordium/web-sdk": 10.0.2 + "@concordium/web-sdk": 12.0.0-devnet-p10.0 live-server: ^1.2.2 languageName: unknown linkType: soft From 48a6c850028a7e8027c28b52f6f1af4a990b24ac Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Wed, 3 Dec 2025 13:02:52 +0200 Subject: [PATCH 02/14] [WAL-1471] Add support for sponsored transactions Update network connection --- .../src/shared/constants/networkConfiguration.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/browser-wallet/src/shared/constants/networkConfiguration.ts b/packages/browser-wallet/src/shared/constants/networkConfiguration.ts index 515a714eb..bc601fa69 100644 --- a/packages/browser-wallet/src/shared/constants/networkConfiguration.ts +++ b/packages/browser-wallet/src/shared/constants/networkConfiguration.ts @@ -28,12 +28,12 @@ export const stagenet: NetworkConfiguration = { }; export const devnet: NetworkConfiguration = { - genesisHash: '1e38963f8098c190a1a10ffd8954ad79f93ca50a843efbb09783f8b4e9155d03', + genesisHash: '5867d1b162172bf671df64fb6f568c573141fd80b7795adc8d91e4337ff2ef9c', name: 'Concordium DevNet', - explorerUrl: 'https://wallet-proxy.devnet-plt-beta.concordium.com', + explorerUrl: 'https://wallet-proxy.devnet-p10-1.concordium.com', grpcPort: 20000, - grpcUrl: 'https://grpc.devnet-plt-beta.concordium.com', - ccdScanUrl: 'https://devnet-plt-beta.ccdscan.io/', + grpcUrl: 'https://grpc.devnet-p10-1.concordium.com', + ccdScanUrl: 'https://devnet-p10-1.ccdscan.io/', }; export const customnet: NetworkConfiguration = { From b628bc73905421b3e78b3dfb4b7d32f3e2307172 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Wed, 3 Dec 2025 13:53:27 +0200 Subject: [PATCH 03/14] [WAL-1471] Add support for sponsored transactions Fix tests --- .../browser-wallet/test/compatibility.test.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/browser-wallet/test/compatibility.test.ts b/packages/browser-wallet/test/compatibility.test.ts index 22dc2a7e1..129950871 100644 --- a/packages/browser-wallet/test/compatibility.test.ts +++ b/packages/browser-wallet/test/compatibility.test.ts @@ -16,7 +16,7 @@ import { EntrypointName, getAccountTransactionHandler, IdStatementBuilder, - InitContractPayload, + InitContractInput, ModuleReference, OpenStatus, Parameter, @@ -25,8 +25,8 @@ import { SchemaVersion, SimpleTransferPayload, SimpleTransferWithMemoPayload, - UpdateContractPayload, - UpdateCredentialsPayload, + UpdateContractInput, + UpdateCredentialsInput, } from '@concordium/web-sdk'; import { SendTransactionInitContractPayload, @@ -183,7 +183,7 @@ describe(sanitizeSendTransactionInput, () => { '23513bcb5dbc81216fa4e12d3165a818e2b8699a1c9ef5c699f46ca3b1024ebf' ); - const expectedPayload: InitContractPayload = { + const expectedPayload: InitContractInput = { moduleRef, maxContractExecutionEnergy: Energy.create(maxContractExecutionEnergy), amount: CcdAmount.fromMicroCcd(amount), @@ -244,7 +244,7 @@ describe(sanitizeSendTransactionInput, () => { const type = AccountTransactionType.Update; const receiveName = `${contractName}.${entrypointName}`; - const expectedPayload: UpdateContractPayload = { + const expectedPayload: UpdateContractInput = { maxContractExecutionEnergy: Energy.create(maxContractExecutionEnergy), amount: CcdAmount.fromMicroCcd(amount), address: ContractAddress.create(contractIndex, contractSubindex), @@ -604,7 +604,8 @@ describe(sanitizeSendTransactionInput, () => { test('Transforms "UpdateCredentials" transaction input as expected', () => { const type = AccountTransactionType.UpdateCredentials; - const payload: UpdateCredentialsPayload = { + // commitments value was removed in type UpdateCredentialsInput + const payload: UpdateCredentialsInput = { newCredentials: [ { index: 1, @@ -622,13 +623,6 @@ describe(sanitizeSendTransactionInput, () => { threshold: 1, keys: { 0: { schemeId: 'ed25519', verifyKey: '030201' } }, }, - commitments: { - cmmPrf: 'cmmPrf', - cmmAttributes: { dob: 'cmmDob' } as Record, - cmmCredCounter: 'cmmCredCounter', - cmmMaxAccounts: 'cmmMaxAccounts', - cmmIdCredSecSharingCoeff: ['cmmIdCredSecSharingCoeff'], - }, arData: { arData: { encIdCredPubShare: 'encIdCredPubShare' } }, }, }, From 015c109dc79ff6c1a4323d0e232b94bccab86587 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Wed, 3 Dec 2025 16:13:54 +0200 Subject: [PATCH 04/14] [WAL-1471] Add support for sponsored transactions Updates after review --- .../browser-wallet/src/shared/utils/payload-helpers.ts | 2 +- packages/browser-wallet/src/wallet-api/compatibility.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/browser-wallet/src/shared/utils/payload-helpers.ts b/packages/browser-wallet/src/shared/utils/payload-helpers.ts index 482d88ed3..0755111a3 100644 --- a/packages/browser-wallet/src/shared/utils/payload-helpers.ts +++ b/packages/browser-wallet/src/shared/utils/payload-helpers.ts @@ -28,7 +28,7 @@ export type HeadlessTransaction = AccountTransactionType, AccountTransactionType.Transfer | AccountTransactionType.Update | AccountTransactionType.InitContract >; - payload: AccountTransactionInput; + payload: Exclude; }; /** diff --git a/packages/browser-wallet/src/wallet-api/compatibility.ts b/packages/browser-wallet/src/wallet-api/compatibility.ts index b9c5cdceb..73c74d1df 100644 --- a/packages/browser-wallet/src/wallet-api/compatibility.ts +++ b/packages/browser-wallet/src/wallet-api/compatibility.ts @@ -33,9 +33,7 @@ import { SimpleTransferPayload, SimpleTransferWithMemoPayload, TokenUpdatePayload, - UpdateCredentialKeysInput, UpdateCredentialsInput, - UpdateCredentialsPayload, } from '@concordium/web-sdk/types'; import { empty } from '@concordium/web-sdk/types/Parameter'; import { IdStatement } from '@concordium/web-sdk/id'; @@ -224,10 +222,7 @@ export interface ConfigureDelegationPayloadV0 extends Omit Date: Wed, 3 Dec 2025 16:19:11 +0200 Subject: [PATCH 05/14] Revert "[WAL-1471] Add support for sponsored transactions" This reverts commit 015c109dc79ff6c1a4323d0e232b94bccab86587. --- .../browser-wallet/src/shared/utils/payload-helpers.ts | 2 +- packages/browser-wallet/src/wallet-api/compatibility.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/browser-wallet/src/shared/utils/payload-helpers.ts b/packages/browser-wallet/src/shared/utils/payload-helpers.ts index 0755111a3..482d88ed3 100644 --- a/packages/browser-wallet/src/shared/utils/payload-helpers.ts +++ b/packages/browser-wallet/src/shared/utils/payload-helpers.ts @@ -28,7 +28,7 @@ export type HeadlessTransaction = AccountTransactionType, AccountTransactionType.Transfer | AccountTransactionType.Update | AccountTransactionType.InitContract >; - payload: Exclude; + payload: AccountTransactionInput; }; /** diff --git a/packages/browser-wallet/src/wallet-api/compatibility.ts b/packages/browser-wallet/src/wallet-api/compatibility.ts index 73c74d1df..b9c5cdceb 100644 --- a/packages/browser-wallet/src/wallet-api/compatibility.ts +++ b/packages/browser-wallet/src/wallet-api/compatibility.ts @@ -33,7 +33,9 @@ import { SimpleTransferPayload, SimpleTransferWithMemoPayload, TokenUpdatePayload, + UpdateCredentialKeysInput, UpdateCredentialsInput, + UpdateCredentialsPayload, } from '@concordium/web-sdk/types'; import { empty } from '@concordium/web-sdk/types/Parameter'; import { IdStatement } from '@concordium/web-sdk/id'; @@ -222,7 +224,10 @@ export interface ConfigureDelegationPayloadV0 extends Omit Date: Fri, 5 Dec 2025 15:51:00 +0200 Subject: [PATCH 06/14] [WAL-1471] Add support for sponsored transactions All changes I have at the moment --- examples/wallet-api/package.json | 3 +- .../wallet-api/sponsored-transaction.html | 157 ++++++++++++ .../src/wallet-api-types.ts | 12 + .../browser-wallet/src/background/index.ts | 10 + .../browser-wallet/src/messaging/message.ts | 2 + .../src/popup/constants/routes.ts | 5 + .../src/popup/popupX/constants/routes.ts | 3 + .../DisplaySingleTransferTokenUpdate.tsx | 200 +++++++++++++++ .../DisplayTransactionPayload.tsx | 231 ++++++++++++++++++ .../SendSponsoredTransaction.scss | 137 +++++++++++ .../SendSponsoredTransaction.tsx | 207 ++++++++++++++++ .../SendSponsoredTransaction/i18n/en.ts | 54 ++++ .../prompts/SendSponsoredTransaction/index.ts | 1 + .../src/popup/popupX/shell/Routes.tsx | 18 ++ .../src/popup/popupX/styles/_elements.scss | 1 + .../shared/utils/message-prompt-handlers.ts | 6 + .../src/popup/shell/i18n/locales/en.ts | 2 + .../src/wallet-api/wallet-api.ts | 33 +++ 18 files changed, 1081 insertions(+), 1 deletion(-) create mode 100644 examples/wallet-api/sponsored-transaction.html create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/index.ts diff --git a/examples/wallet-api/package.json b/examples/wallet-api/package.json index 204e7c469..3349b113f 100644 --- a/examples/wallet-api/package.json +++ b/examples/wallet-api/package.json @@ -9,7 +9,8 @@ "start:sign-message": "FILE=sign-message.html yarn start", "start:sign-cis3-message": "FILE=sign-cis3-message.html yarn start", "start:web3-id": "FILE=web3-id.html yarn start", - "start:sign-token-update": "FILE=sign-token-update.html yarn start" + "start:sign-token-update": "FILE=sign-token-update.html yarn start", + "start:sponsored-transaction": "FILE=sponsored-transaction.html yarn start" }, "dependencies": { "@concordium/web-sdk": "12.0.0-devnet-p10.0" diff --git a/examples/wallet-api/sponsored-transaction.html b/examples/wallet-api/sponsored-transaction.html new file mode 100644 index 000000000..7be42c20b --- /dev/null +++ b/examples/wallet-api/sponsored-transaction.html @@ -0,0 +1,157 @@ + + + + My cool dapp + + + + + + + + +
+ +

Account address:

+
+
+ +
+

Sponsor Config:

+ Sponsor Account: + +
+ Sponsor Private Key: + +
+

Recipient Config:

+ Recipient Address: + +
+
+ + + + diff --git a/packages/browser-wallet-api-helpers/src/wallet-api-types.ts b/packages/browser-wallet-api-helpers/src/wallet-api-types.ts index eba8f2998..9bd942333 100644 --- a/packages/browser-wallet-api-helpers/src/wallet-api-types.ts +++ b/packages/browser-wallet-api-helpers/src/wallet-api-types.ts @@ -254,6 +254,18 @@ interface MainWalletApi { type: LaxNumberEnumValue, payload: ConfigureDelegationPayload ): Promise; + /** + * Sends a transaction to the Concordium Wallet and awaits the users action. Note that a header is not sent, and will be constructed by the wallet itself. + * Note that if the user rejects signing the transaction, this will throw an error. + * @param accountAddress the address of the account that should sign the transaction + * @param type the type of transaction that is to be signed and sent. + * @param payload the payload of the transaction to be signed and sent. Note that for smart contract transactions, the payload should not contain the parameters, those should instead be provided in the subsequent argument instead. + */ + sendSponsoredTransaction( + accountAddress: AccountAddressSource, + type: LaxNumberEnumValue, + payload: SimpleTransferPayload + ): Promise; /** * Sends a message to the Concordium Wallet and awaits the users action. If the user signs the message, this will resolve to the signature. * Note that if the user rejects signing the message, this will throw an error. diff --git a/packages/browser-wallet/src/background/index.ts b/packages/browser-wallet/src/background/index.ts index d1d58b635..9ff764790 100644 --- a/packages/browser-wallet/src/background/index.ts +++ b/packages/browser-wallet/src/background/index.ts @@ -282,6 +282,8 @@ const ensureMessageWithSchemaParse: RunCondition const ensureTransactionPayloadParse: RunCondition> = async (msg) => { const payload = msg.payload as BackgroundSendTransactionPayload; + console.log('ensureTransactionPayloadParse'); + try { parsePayload(payload.type, payload.payload, payload.parameters, payload.schema, payload.schemaVersion); } catch (e) { @@ -531,6 +533,14 @@ forwardToPopup( undefined, withPromptEnd ); +forwardToPopup( + MessageType.SendSponsoredTransaction, + InternalMessageType.SendSponsoredTransaction, + runConditionComposer(runIfAccountIsAllowlisted, async () => ({ run: true }), withPromptStart()), + appendUrlToPayload, + undefined, + withPromptEnd +); forwardToPopup( MessageType.SignMessage, InternalMessageType.SignMessage, diff --git a/packages/browser-wallet/src/messaging/message.ts b/packages/browser-wallet/src/messaging/message.ts index b06f5d18d..f94e303e5 100644 --- a/packages/browser-wallet/src/messaging/message.ts +++ b/packages/browser-wallet/src/messaging/message.ts @@ -7,6 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; */ export enum MessageType { SendTransaction = 'M_SendTransaction', + SendSponsoredTransaction = 'M_SendSponsoredTransaction', SignMessage = 'M_SignMessage', GetAccounts = 'M_GetAccounts', GetSelectedAccount = 'M_GetSelectedAccount', @@ -29,6 +30,7 @@ export enum InternalMessageType { Init = 'I_Init', PopupReady = 'I_PopupReady', SendTransaction = 'I_SendTransaction', + SendSponsoredTransaction = 'I_SendSponsoredTransaction', SignMessage = 'I_SignMessage', Connect = 'I_Connect', TestPopupOpen = 'I_TestPopupOpen', diff --git a/packages/browser-wallet/src/popup/constants/routes.ts b/packages/browser-wallet/src/popup/constants/routes.ts index 2a52f5335..47cdaf4aa 100644 --- a/packages/browser-wallet/src/popup/constants/routes.ts +++ b/packages/browser-wallet/src/popup/constants/routes.ts @@ -6,6 +6,8 @@ type RouteChildren = { [key: string]: RouteNode | RoutePath; }; +// ToDo sendSponsoredTransaction not implemented in OldUI +// Need redirect to new UI, or Sunset OldUI export const relativeRoutes = { home: { path: '/', @@ -63,6 +65,9 @@ export const relativeRoutes = { sendTransaction: { path: 'send-transaction', }, + sendSponsoredTransaction: { + path: 'send-sponsored-transaction', + }, endIdentityIssuance: { path: 'end-identity-issuance', }, diff --git a/packages/browser-wallet/src/popup/popupX/constants/routes.ts b/packages/browser-wallet/src/popup/popupX/constants/routes.ts index 6d55e07f6..b1f5b37b7 100644 --- a/packages/browser-wallet/src/popup/popupX/constants/routes.ts +++ b/packages/browser-wallet/src/popup/popupX/constants/routes.ts @@ -410,6 +410,9 @@ export const relativeRoutes = { sendTransaction: { path: 'sendTransaction', }, + sendSponsoredTransaction: { + path: 'sendSponsoredTransaction', + }, addWeb3IdCredential: { path: 'add-web3id-credential', }, diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx new file mode 100644 index 000000000..7acbfcf68 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx @@ -0,0 +1,200 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { CcdAmount, TokenUpdatePayload, AccountInfo } from '@concordium/web-sdk'; +import { Cbor, CborMemo, TokenOperationType, TokenTransferOperation } from '@concordium/web-sdk/plt'; +import { WalletCredential } from '@shared/storage/types'; +import { displaySplitAddress, useCredential, useIdentityName } from '@popup/shared/utils/account-helpers'; +import { displayAsCcd, getPublicAccountAmounts } from 'wallet-common-helpers'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import Card from '@popup/popupX/shared/Card'; +import Button from '@popup/popupX/shared/Button'; +import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext'; +import { ToggleAdvanced } from '@popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload'; +import { formatTokenAmount } from '@popup/popupX/shared/utils/helpers'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; +import { logError } from '@shared/utils/log-helpers'; + +async function getWebsiteTitle(url: string): Promise { + try { + const { origin } = new URL(url); + const response = await fetch(origin); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const html = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const titleElement = doc.querySelector('title'); + return titleElement ? titleElement.innerText : displayUrl(url); + } catch (error) { + logError(`Error fetching or parsing URL: ${error}`); + return displayUrl(url); + } +} + +const getPltBalance = (accountInfo: AccountInfo | undefined, tokenId: string) => + accountInfo?.accountTokens.find((token) => token.id.toString() === tokenId); + +function AccountInfoCard({ credential, tokenId }: { credential: WalletCredential; tokenId: string }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + const accountInfo = useAccountInfo(credential); + const identityName = useIdentityName(credential); + const { atDisposal } = getPublicAccountAmounts(accountInfo); + const accountAtDisposal = formatTokenAmount(atDisposal, 6, 2, 2); + const { credName, address } = credential; + const { + state: { + balance: { value, decimals }, + }, + } = getPltBalance(accountInfo, tokenId) || { state: { balance: { value: 0n, decimals: 0 } } }; + const pltFormated = formatTokenAmount(value, decimals, 2, 2); + + return ( + + + {credName && {credName}} + {displaySplitAddress(address)} + + + {identityName} + + + + + {t('account.balance')} + {pltFormated} {tokenId} + + + + + {t('account.atDisposal')} + {accountAtDisposal} CCD + + + + ); +} + +type TransferInfoProps = { + tokenId: string; + amount: string; + recipient: string; + cost?: bigint | string | CcdAmount.Type; +}; + +function TransferInfo({ tokenId, amount, recipient, cost }: TransferInfoProps) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + + return ( + + + + + {t('payload.amount')} + + {amount} {tokenId} + + + + {t('payload.fee')} + {cost ? displayAsCcd(cost, false, true) : t('payload.unknown')} + + + ); +} + +type TransferInfoAdvancedProps = { + payload: string; + tokenId: string; + accountAddress: string; +}; + +function TransferInfoAdvanced({ payload, tokenId, accountAddress }: TransferInfoAdvancedProps) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + + return ( +
+ + + {t('tokenUpdate')} + + + + + +
+ ); +} + +const parsePayload = (payload: TokenUpdatePayload) => { + const { tokenId, operations } = payload; + const decodedOperations = Cbor.decode(operations) as TokenTransferOperation[]; + const { amount, recipient, memo: encodedMemo } = decodedOperations[0][TokenOperationType.Transfer]; + const decodedMemo = encodedMemo ? CborMemo.parse(encodedMemo as CborMemo.Type) : undefined; + + const stringifiedPayload = JSON.stringify( + [{ [TokenOperationType.Transfer]: { amount, recipient, memo: decodedMemo } }], + null, + 2 + ); + return { + tokenId: tokenId.toString(), + amount: amount.toString(), + recipient: recipient.toString(), + stringifiedPayload, + }; +}; + +type DisplaySingleTransferProps = { + url: string; + cost?: bigint | string | CcdAmount.Type; + payload: TokenUpdatePayload; + accountAddress: string; + signHandler: () => void; + rejectHandler: () => void; +}; + +export default function DisplaySingleTransferTokenUpdate({ + url, + cost, + payload, + accountAddress, + signHandler, + rejectHandler, +}: DisplaySingleTransferProps) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + const [webPageTitle, setWebPageTitle] = useState(''); + const { tokenId, amount, recipient, stringifiedPayload } = parsePayload(payload); + const credential = useCredential(accountAddress); + + useEffect(() => { + getWebsiteTitle(url) + .then((title) => { + setWebPageTitle(title); + }) + .catch((error) => logError(`Failed to get title: ${error}`)); + }, []); + + if (!credential) { + return null; + } + + return ( + + + + SPONSORED PAGE + {webPageTitle} + + + + + + + + + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload.tsx new file mode 100644 index 000000000..17f7697c7 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload.tsx @@ -0,0 +1,231 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { + AccountTransactionPayload, + AccountTransactionType, + CcdAmount, + DeployModulePayload, + InitContractInput, + RegisterDataPayload, + sha256, + SimpleTransferPayload, + TokenUpdatePayload, + UpdateContractInput, +} from '@concordium/web-sdk'; +import { Cbor, CborMemo, TokenOperationType } from '@concordium/web-sdk/plt'; +import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; +import { useTranslation } from 'react-i18next'; +import { chunkString, displayAsCcd } from 'wallet-common-helpers'; +import * as JSONBig from 'json-bigint'; +import { decode } from 'cbor2'; +import Card from '@popup/popupX/shared/Card'; +import Text from '@popup/popupX/shared/Text'; +import SideArrow from '@assets/svgX/side-arrow.svg'; +import Parameter from '@popup/popupX/shared/Parameter'; +import { cborDecode } from '@popup/popupX/shared/utils/helpers'; + +export function DisplayParameters({ parameters }: { parameters?: SmartContractParameters }) { + const hasParameters = parameters !== undefined && parameters !== null; + if (!hasParameters) return null; + return ; +} + +/** + * Displays an overview of a simple transfer. + */ +function DisplaySimpleTransfer({ payload }: { payload: SimpleTransferPayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + return ( + <> + + + + ); +} + +/** + * Displays an overview of a update contract transaction. + */ +function DisplayUpdateContract({ payload }: { payload: Omit }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + return ( + <> + + + + + + ); +} + +/** + * Displays an overview of a init contract transaction. + */ +function DisplayInitContract({ payload }: { payload: Omit }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + + return ( + <> + + + + + + ); +} + +/** + * Displays an overview of a register data. + */ +function DisplayRegisterData({ payload }: { payload: RegisterDataPayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + const [decoded, setDecoded] = useState(); + + useEffect(() => { + try { + setDecoded(decode(payload.data.data)); + } catch { + // display raw if unable to decode + } + }, []); + + const title = `${t('data')}${!decoded ? t('rawData') : ''}`; + const value = JSONBig.stringify(decoded) || payload.data.toJSON(); + + return ; +} + +/** + * Displays an overview of a deploy module transaction. + */ +function DisplayDeployModule({ payload }: { payload: DeployModulePayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + const hash = useMemo(() => sha256([payload.source]).toString('hex'), []); + const { version } = payload; + + return ( + <> + {version && } + + + ); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function displayValue(value: any) { + if (CcdAmount.instanceOf(value)) { + return displayAsCcd(value.microCcdAmount); + } + return value.toString(); +} + +function operationsCborDecoder(value: Cbor.Type) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.operations' }); + const getTitle = (key: TokenOperationType) => t(key) || key; + + if (Object.keys(cborDecode(value.toString())).length) { + const decoded = Cbor.decode(value) as object[]; + + const withDecodedMemo = decoded.map((item) => + Object.entries(item).reduce((acc, [key, operationValue]) => { + const memo = operationValue.memo ? CborMemo.parse(operationValue.memo) : undefined; + const memoString = typeof memo === 'object' ? JSON.stringify(memo, null, 2) : memo; + return { + ...acc, + [key]: { + ...operationValue, + ...(!!memo && { memo: memoString }), + }, + }; + }, {}) + ); + + const operationsList = withDecodedMemo.map((item: { [key: string]: object }) => { + const operation = Object.keys(item)[0] as TokenOperationType; + return ( + + + {getTitle(operation)} + + {Object.entries(item[operation]).map(([key, operationValue]) => ( + + ))} + + ); + }); + + return
{operationsList}
; + } + + return value.toString(); +} + +export function ToggleAdvanced() { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + return ( + + ); +} + +/** + * Displays an overview of token update transaction payload. + */ +function DisplayTokenUpdate({ payload }: { payload: TokenUpdatePayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + return ( + <> + + } value={operationsCborDecoder(payload.operations)} /> + + ); +} + +/** + * Displays an overview of any transaction payload. + */ +function DisplayGenericPayload({ payload }: { payload: AccountTransactionPayload }) { + return ( + <> + {Object.entries(payload).map(([key, value]) => ( + + ))} + + ); +} + +export default function DisplayTransactionPayload({ + payload, + type, +}: { + type: AccountTransactionType; + payload: AccountTransactionPayload; +}) { + switch (type) { + case AccountTransactionType.Transfer: + return ; + case AccountTransactionType.Update: + return ; + case AccountTransactionType.InitContract: + return ; + case AccountTransactionType.RegisterData: + return ; + case AccountTransactionType.DeployModule: + return ; + case AccountTransactionType.TokenUpdate: + return ; + default: + return ; + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss new file mode 100644 index 000000000..84247ba25 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss @@ -0,0 +1,137 @@ +.send-transaction-x { + .text__main { + color: $color-mineral-2; + + &_medium { + color: $color-white; + } + + .white { + color: $color-white; + } + } + + .card-x.grey { + margin-top: rem(16px); + margin-bottom: rem(32px); + + .row.details:has(+ .parameter-x) { + border: unset; + } + } + + #toggle-collapse { + display: none; + } + + .collapse-trigger { + cursor: pointer; + + svg { + width: 12px; + height: 12px; + transform: scale(2) translateY(1px); + margin-left: rem(8px); + + path { + fill: $color-mineral-3; + } + } + } + + .page__main:has(#toggle-collapse:checked), + .row.details:has(#toggle-collapse:checked) { + .operations-list { + max-height: unset; + } + + .collapse-trigger svg { + transform: scale(2) translateY(1px) rotate(180deg); + } + } + + .operations-list { + display: flex; + overflow: hidden; + max-height: 0; + flex-direction: column; + gap: rem(12px); + + .card-x.grey { + margin-top: 0; + margin-bottom: 0; + } + } + + &.single-transaction-token-update { + .page__top { + margin-bottom: unset; + } + + .collapse-trigger { + color: $color-grey-main; + margin-bottom: rem(16px); + + svg { + transform: scale(2) translateY(1px); + margin-left: rem(8px); + + path { + fill: $color-grey-main; + } + } + } + + .card-x { + .row { + &.amounts { + justify-content: space-between; + border-bottom: unset; + padding-bottom: unset; + + .capture__main_small:first-of-type { + color: $semantic-content-accent-secondary; + } + + .capture__main_small { + color: $color-white; + } + } + } + + &.grey { + margin-bottom: rem(24px); + } + + &.gradient { + margin-top: rem(24px); + + .text__main { + color: $color-grey-1; + line-height: rem(20px); + } + + .text__main_regular { + color: $color-grey-1; + } + + .divider { + width: 100%; + margin-bottom: rem(8px); + border-bottom: 1px solid $color-input-border; + } + + .row { + padding: unset; + align-items: baseline; + gap: rem(12px); + border-bottom: unset; + } + } + } + + .page__footer { + flex-direction: row; + } + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx new file mode 100644 index 000000000..b5d1e582a --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx @@ -0,0 +1,207 @@ +import React, { useCallback, useContext, useEffect, useMemo } from 'react'; +import { BackgroundSendTransactionPayload } from '@shared/utils/types'; +import { useLocation } from 'react-router-dom'; +import { Trans, useTranslation } from 'react-i18next'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { addToastAtom } from '@popup/state'; +import { grpcClientAtom } from '@popup/store/settings'; +import { fullscreenPromptContext } from '@popup/popupX/page-layouts/FullscreenPromptLayout'; +import { useUpdateAtom } from 'jotai/utils'; +import { addPendingTransactionAtom } from '@popup/store/transactions'; +import { useBlockChainParameters } from '@popup/shared/BlockChainParametersProvider'; +import { usePrivateKey } from '@popup/shared/utils/account-helpers'; +import { parse, parsePayload } from '@shared/utils/payload-helpers'; +import * as JSONBig from 'json-bigint'; +import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; +import { + AccountAddress, + AccountTransactionType, + TokenUpdatePayload, + convertEnergyToMicroCcd, + getEnergyCost, + AccountTransactionPayload, + Transaction, + buildBasicAccountSigner, +} from '@concordium/web-sdk'; +import { Cbor, TokenOperationType } from '@concordium/web-sdk/plt'; +import { displayAsCcd, getPublicAccountAmounts } from 'wallet-common-helpers'; +import { + createPendingTransactionFromAccountTransaction, + getDefaultExpiry, + getTransactionAmount, + getTransactionTypeName, + sendTransaction, +} from '@popup/shared/utils/transaction-helpers'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; +import Button from '@popup/popupX/shared/Button'; +import Card from '@popup/popupX/shared/Card'; +import DisplayTransactionPayload, { + DisplayParameters, +} from '@popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload'; +import DisplaySingleTransferTokenUpdate from '@popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate'; + +const isSingleTransferTokenUpdate = (transactionType: AccountTransactionType, payload: AccountTransactionPayload) => { + const isTokenUpdate = transactionType === AccountTransactionType.TokenUpdate; + if (isTokenUpdate) { + const { operations } = payload as TokenUpdatePayload; + const decodedOperations = Cbor.decode(operations) as object[]; + return decodedOperations.length === 1 && Object.keys(decodedOperations[0])[0] === TokenOperationType.Transfer; + } + return false; +}; + +interface Location { + state: { + payload: BackgroundSendTransactionPayload; + }; +} + +interface Props { + onSubmit(hash: string): void; + onReject(): void; +} + +export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) { + const { state } = useLocation() as Location; + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + const addToast = useSetAtom(addToastAtom); + const client = useAtomValue(grpcClientAtom); + const { withClose, onClose } = useContext(fullscreenPromptContext); + const addPendingTransaction = useUpdateAtom(addPendingTransactionAtom); + const chainParameters = useBlockChainParameters(); + + console.log('state', state); + + const { accountAddress, url } = state.payload; + const key = usePrivateKey(accountAddress); + + const { type: transactionType, payload } = useMemo( + () => + parsePayload( + state.payload.type, + state.payload.payload, + state.payload.parameters, + state.payload.schema, + state.payload.schemaVersion + ), + [JSON.stringify(state.payload)] + ); + const parameters = useMemo( + () => + state.payload.parameters === undefined + ? undefined + : (JSONBig.parse(state.payload.parameters) as SmartContractParameters), + [state.payload.parameters] + ); + + const cost = useMemo(() => { + if (chainParameters) { + const energy = getEnergyCost(transactionType, payload); + return convertEnergyToMicroCcd(energy, chainParameters); + } + return undefined; + }, [transactionType, chainParameters]); + + useEffect(() => onClose(onReject), [onClose, onReject]); + + const handleSubmit = useCallback(async () => { + if (!accountAddress) { + throw new Error(t('errors.missingAccount')); + } + if (!key) { + throw new Error(t('errors.missingKey')); + } + + const sender = AccountAddress.fromBase58(accountAddress); + const accountInfo = await client.getAccountInfo(sender); + // if ( + // getPublicAccountAmounts(accountInfo).atDisposal < + // getTransactionAmount(transactionType, payload) + (cost?.microCcdAmount || 0n) + // ) { + // throw new Error(t('errors.insufficientFunds')); + // } + + const nonce = await client.getNextAccountNonce(sender); + + if (!nonce) { + throw new Error(t('errors.missingNonce')); + } + + const header = { + expiry: getDefaultExpiry(), + sender, + nonce: nonce.nonce, + }; + + // ToDo magic happens here + const transaction = parse(state.payload.payloadSponsored); + const sponsoredTransaction = Transaction.signableFromJSON(transaction); + const signed = await Transaction.signAndFinalize(sponsoredTransaction, buildBasicAccountSigner(key)); + const hash = await client.sendTransaction(signed); + // end of magic + + const pending = createPendingTransactionFromAccountTransaction( + transaction, + hash.toString(), + cost?.microCcdAmount + ); + await addPendingTransaction(pending); + + return hash.toString(); + }, [payload, key, cost]); + + const rejectHandler = withClose(onReject); + const signHandler = () => { + handleSubmit() + .then(withClose(onSubmit)) + .catch((e) => addToast(e.message)); + }; + + if (isSingleTransferTokenUpdate(transactionType, payload)) { + return ( + + ); + } + + return ( + + + + SPONSORED PAGE + + }} + values={{ dApp: displayUrl(url) }} + /> + + + + {getTransactionTypeName(transactionType)} + + + + + + + + + + + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts new file mode 100644 index 000000000..d7ea61792 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts @@ -0,0 +1,54 @@ +const t = { + reject: 'Reject', + sign: 'Sign & Submit', + signTransaction: '<1>{{dApp}} suggests a transaction', + signRequest: 'Signature request', + transferRequest: 'Transfer Request', + tokenUpdate: 'Token Update', + method: 'Method', + advanced: 'Advanced', + approve: 'Approve', + account: { + balance: 'Balance: ', + atDisposal: 'At Disposal: ', + }, + errors: { + missingAccount: 'Missing account address', + missingKey: 'Missing key for the chosen address', + insufficientFunds: 'Account has insufficient funds for the transaction', + missingNonce: 'No nonce was found for the chosen account', + }, + payload: { + amount: 'Amount', + receiver: 'Receiver', + contractIndex: 'Contract index (subindex)', + receiveName: 'Contract and function name', + maxEnergy: 'Max energy allowed', + nrg: 'NRG', + noParameter: 'No parameters', + sender: 'Sender account', + cost: 'Estimated transaction fee', + unknown: 'Unknown', + moduleReference: 'Module reference', + contractName: 'Contract name', + data: 'Data', + rawData: ': (Unable to be decoded)', + version: 'Version', + tokenId: 'Token Id', + operations: 'Operations', + fee: 'Approximate fee', + }, + operations: { + transfer: 'Transfer', + mint: 'Mint', + burn: 'Burn', + addAllowList: 'Add to allow list', + removeAllowList: 'Remove from allow list', + addDenyList: 'Add to deny list', + removeDenyList: 'Remove from deny list', + pause: 'Pause', + unpause: 'Unpause', + }, +}; + +export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/index.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/index.ts new file mode 100644 index 000000000..559b4045d --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/index.ts @@ -0,0 +1 @@ +export { default } from './SendSponsoredTransaction'; diff --git a/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx b/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx index b6e61555e..d56681344 100644 --- a/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx +++ b/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx @@ -44,6 +44,7 @@ import AddTokens from '@popup/popupX/pages/prompts/AddTokens'; import SignCis3Message from '@popup/popupX/pages/prompts/SignCis3Message'; import SignMessage from '@popup/popupX/pages/prompts/SignMessage'; import SendTransaction from '@popup/popupX/pages/prompts/SendTransaction'; +import SendSponsoredTransaction from '@popup/popupX/pages/prompts/SendSponsoredTransaction'; import ExternalRequestLayout from '@popup/popupX/page-layouts/ExternalRequestLayout'; import { AddToken, ManageTokenList } from '@popup/popupX/pages/ManageTokens'; import { Nft, NftDetails, NftRaw } from 'src/popup/popupX/pages/Nft'; @@ -83,6 +84,7 @@ export default function Routes({ messagePromptHandlers }: { messagePromptHandler handleSignCIS3MessageResponse, handleSignMessageResponse, handleSendTransactionResponse, + handleSendSponsoredTransactionResponse, handleAddWeb3IdCredentialResponse, handleWeb3IdProofResponse, handleIdProofResponse, @@ -351,6 +353,22 @@ export default function Routes({ messagePromptHandlers }: { messagePromptHandler } /> } + />{' '} + + handleSendSponsoredTransactionResponse({ success: true, result: hash }) + } + onReject={() => + handleSendSponsoredTransactionResponse({ + success: false, + message: 'Signing was rejected', + }) + } + /> + } /> } /> } /> diff --git a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss index b3baab921..7e7534906 100644 --- a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss +++ b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss @@ -67,6 +67,7 @@ @import '../pages/prompts/SignCis3Message/SignCis3Message'; @import '../pages/prompts/SignMessage/SignMessage'; @import '../pages/prompts/SendTransaction/SendTransaction'; +@import '../pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction'; @import '../pages/prompts/AddWeb3IdCredential/AddWeb3IdCredential'; @import '../pages/prompts/IdProofRequest'; @import '../pages/prompts/VerifiablePresentationRequest/VerifiablePresentationRequest'; diff --git a/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts b/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts index 403d77af1..481d48268 100644 --- a/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts +++ b/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts @@ -78,6 +78,7 @@ export type MessagePromptHandlersType = { handleConnectAccountsResponse: (response: MessageStatusWrapper) => void; handleAddWeb3IdCredentialResponse: (response: MessageStatusWrapper) => void; handleSendTransactionResponse: (response: MessageStatusWrapper) => void; + handleSendSponsoredTransactionResponse: (response: MessageStatusWrapper) => void; handleSignMessageResponse: (response: MessageStatusWrapper) => void; handleSignCIS3MessageResponse: (response: MessageStatusWrapper) => void; handleAddTokensResponse: (response: MessageStatusWrapper) => void; @@ -100,6 +101,10 @@ export function useMessagePromptHandlers(): MessagePromptHandlersType { InternalMessageType.SendTransaction, 'sendTransaction' ); + const handleSendSponsoredTransactionResponse = useMessagePrompt>( + InternalMessageType.SendSponsoredTransaction, + 'sendSponsoredTransaction' + ); const handleSignMessageResponse = useMessagePrompt>( InternalMessageType.SignMessage, 'signMessage' @@ -140,6 +145,7 @@ export function useMessagePromptHandlers(): MessagePromptHandlersType { handleConnectAccountsResponse, handleAddWeb3IdCredentialResponse, handleSendTransactionResponse, + handleSendSponsoredTransactionResponse, handleSignMessageResponse, handleSignCIS3MessageResponse, handleAddTokensResponse, diff --git a/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts b/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts index 3dc3f630d..a92878fcb 100644 --- a/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts +++ b/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts @@ -58,6 +58,7 @@ import addTokensX from '@popup/popupX/pages/prompts/AddTokens/i18n/en'; import signCis3 from '@popup/popupX/pages/prompts/SignCis3Message/i18n/en'; import signMessageX from '@popup/popupX/pages/prompts/SignMessage/i18n/en'; import sendTransactionX from '@popup/popupX/pages/prompts/SendTransaction/i18n/en'; +import sendSponsoredTransactionX from '@popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en'; import submittedTransaction from '@popup/popupX/pages/SubmittedTransaction/i18n/en'; import nft from '@popup/popupX/pages/Nft/i18n/en'; import idIssuance from '@popup/popupX/pages/IdIssuance/i18n/en'; @@ -127,6 +128,7 @@ const t = { signCis3, signMessageX, sendTransactionX, + sendSponsoredTransactionX, addWeb3IdCredentialX, verifiablePresentationRequest, idProofRequestX, diff --git a/packages/browser-wallet/src/wallet-api/wallet-api.ts b/packages/browser-wallet/src/wallet-api/wallet-api.ts index 69d3ef71c..07a3f38b7 100644 --- a/packages/browser-wallet/src/wallet-api/wallet-api.ts +++ b/packages/browser-wallet/src/wallet-api/wallet-api.ts @@ -180,6 +180,39 @@ class WalletApi extends EventEmitter implements IWalletApi { return response.result; } + /** + * Sends a sponsored transaction to the Concordium Wallet and awaits the users action + */ + public async sendSponsoredTransaction( + accountAddress: AccountAddressSource, + type: AccountTransactionType, + payload: SendTransactionPayload, + parameters?: SmartContractParameters, + schema?: SchemaSource, + schemaVersion?: SchemaVersion + ): Promise { + // ToDo need update for input sanitize + // const input = sanitizeSendTransactionInput(accountAddress, type, payload, parameters, schema, schemaVersion); + + const response = await this.messageHandler.sendMessage>( + MessageType.SendSponsoredTransaction, + { + // ...input, + type, + accountAddress, + payload: stringify(payload.payload), + parameters: JSONBig.stringify(parameters), + payloadSponsored: stringify(payload), + } + ); + + if (!response.success) { + throw new Error(response.message); + } + + return response.result; + } + private handleEvent(type: EventType) { this.messageHandler.handleMessage(createEventTypeFilter(type), (msg) => this.emit(type, msg.payload)); } From 82c7714c165772cbfb98c51b0e1b284ba92af106 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Fri, 5 Dec 2025 16:57:17 +0200 Subject: [PATCH 07/14] [WAL-1471] Add support for sponsored transactions Disable TS checks --- .../SendSponsoredTransaction/SendSponsoredTransaction.tsx | 1 + packages/browser-wallet/src/wallet-api/wallet-api.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx index b5d1e582a..25fb029fa 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx @@ -136,6 +136,7 @@ export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) }; // ToDo magic happens here + // @ts-ignore const transaction = parse(state.payload.payloadSponsored); const sponsoredTransaction = Transaction.signableFromJSON(transaction); const signed = await Transaction.signAndFinalize(sponsoredTransaction, buildBasicAccountSigner(key)); diff --git a/packages/browser-wallet/src/wallet-api/wallet-api.ts b/packages/browser-wallet/src/wallet-api/wallet-api.ts index 07a3f38b7..ce8cde192 100644 --- a/packages/browser-wallet/src/wallet-api/wallet-api.ts +++ b/packages/browser-wallet/src/wallet-api/wallet-api.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { InjectedMessageHandler, createEventTypeFilter, MessageType, MessageStatusWrapper } from '@messaging'; import { AccountAddress, From ba80854664cbe5ea9bab1640120989919ffe2739 Mon Sep 17 00:00:00 2001 From: Mani Singh Date: Mon, 15 Dec 2025 10:44:12 -0700 Subject: [PATCH 08/14] Sponsored Transaction UI updates --- .../wallet-api/sponsored-transaction.html | 12 ++-- .../FullscreenPromptLayout.tsx | 2 +- .../DisplaySingleTransferTokenUpdate.tsx | 67 ++++++------------- .../SendSponsoredTransaction/i18n/en.ts | 2 +- .../browser-wallet/src/shared/utils/types.ts | 1 + 5 files changed, 29 insertions(+), 55 deletions(-) diff --git a/examples/wallet-api/sponsored-transaction.html b/examples/wallet-api/sponsored-transaction.html index 7be42c20b..8ed02f45e 100644 --- a/examples/wallet-api/sponsored-transaction.html +++ b/examples/wallet-api/sponsored-transaction.html @@ -87,7 +87,7 @@ Cbor, TokenId, Transaction, - CborAccountAddress + CborAccountAddress, } = concordiumSDK; const ops = [ @@ -97,7 +97,9 @@ value: '1000000', decimals: 6, }), - recipient: CborAccountAddress.fromAccountAddress(AccountAddress.fromBase58(recipient.value)), + recipient: CborAccountAddress.fromAccountAddress( + AccountAddress.fromBase58(recipient.value) + ), memo: CborMemo.fromString('Some text for memo'), }, }, @@ -141,14 +143,14 @@

Account address:


Sponsor Config:

Sponsor Account: - +
Sponsor Private Key: - +

Recipient Config:

Recipient Address: - +

diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx index c1da8704b..5ae39f883 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx @@ -52,7 +52,7 @@ export default function FullscreenPromptLayout({ children }: { children: ReactNo (action) => (...args) => { action(...args); - close(); + // close(); }, [close] ); diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx index 7acbfcf68..258d94626 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx @@ -1,19 +1,19 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { CcdAmount, TokenUpdatePayload, AccountInfo } from '@concordium/web-sdk'; +import { TokenUpdatePayload, AccountInfo } from '@concordium/web-sdk'; import { Cbor, CborMemo, TokenOperationType, TokenTransferOperation } from '@concordium/web-sdk/plt'; import { WalletCredential } from '@shared/storage/types'; import { displaySplitAddress, useCredential, useIdentityName } from '@popup/shared/utils/account-helpers'; -import { displayAsCcd, getPublicAccountAmounts } from 'wallet-common-helpers'; import Page from '@popup/popupX/shared/Page'; import Text from '@popup/popupX/shared/Text'; import Card from '@popup/popupX/shared/Card'; import Button from '@popup/popupX/shared/Button'; import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext'; -import { ToggleAdvanced } from '@popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload'; import { formatTokenAmount } from '@popup/popupX/shared/utils/helpers'; import { displayUrl } from '@popup/shared/utils/string-helpers'; import { logError } from '@shared/utils/log-helpers'; +import Tooltip from '@popup/popupX/shared/Tooltip/Tooltip'; +import Info from '@assets/svgX/info.svg'; async function getWebsiteTitle(url: string): Promise { try { @@ -40,8 +40,6 @@ function AccountInfoCard({ credential, tokenId }: { credential: WalletCredential const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); const accountInfo = useAccountInfo(credential); const identityName = useIdentityName(credential); - const { atDisposal } = getPublicAccountAmounts(accountInfo); - const accountAtDisposal = formatTokenAmount(atDisposal, 6, 2, 2); const { credName, address } = credential; const { state: { @@ -66,12 +64,6 @@ function AccountInfoCard({ credential, tokenId }: { credential: WalletCredential {pltFormated} {tokenId} - - - {t('account.atDisposal')} - {accountAtDisposal} CCD - - ); } @@ -79,17 +71,13 @@ function AccountInfoCard({ credential, tokenId }: { credential: WalletCredential type TransferInfoProps = { tokenId: string; amount: string; - recipient: string; - cost?: bigint | string | CcdAmount.Type; + sponsoredAccount: string; }; -function TransferInfo({ tokenId, amount, recipient, cost }: TransferInfoProps) { +function TransferInfo({ tokenId, amount, sponsoredAccount }: TransferInfoProps) { const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); - return ( - - {t('payload.amount')} @@ -98,35 +86,21 @@ function TransferInfo({ tokenId, amount, recipient, cost }: TransferInfoProps) { {t('payload.fee')} - {cost ? displayAsCcd(cost, false, true) : t('payload.unknown')} + + + Free Transaction + + ); } -type TransferInfoAdvancedProps = { - payload: string; - tokenId: string; - accountAddress: string; -}; - -function TransferInfoAdvanced({ payload, tokenId, accountAddress }: TransferInfoAdvancedProps) { - const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); - - return ( -
- - - {t('tokenUpdate')} - - - - - -
- ); -} - const parsePayload = (payload: TokenUpdatePayload) => { const { tokenId, operations } = payload; const decodedOperations = Cbor.decode(operations) as TokenTransferOperation[]; @@ -148,24 +122,24 @@ const parsePayload = (payload: TokenUpdatePayload) => { type DisplaySingleTransferProps = { url: string; - cost?: bigint | string | CcdAmount.Type; payload: TokenUpdatePayload; accountAddress: string; + sponsoredAccount: string; signHandler: () => void; rejectHandler: () => void; }; export default function DisplaySingleTransferTokenUpdate({ url, - cost, payload, accountAddress, + sponsoredAccount, signHandler, rejectHandler, }: DisplaySingleTransferProps) { const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); const [webPageTitle, setWebPageTitle] = useState(''); - const { tokenId, amount, recipient, stringifiedPayload } = parsePayload(payload); + const { tokenId, amount } = parsePayload(payload); const credential = useCredential(accountAddress); useEffect(() => { @@ -184,12 +158,9 @@ export default function DisplaySingleTransferTokenUpdate({ - SPONSORED PAGE {webPageTitle} - - - + diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts index d7ea61792..8481de856 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts @@ -36,7 +36,7 @@ const t = { version: 'Version', tokenId: 'Token Id', operations: 'Operations', - fee: 'Approximate fee', + fee: 'Transaction fee', }, operations: { transfer: 'Transfer', diff --git a/packages/browser-wallet/src/shared/utils/types.ts b/packages/browser-wallet/src/shared/utils/types.ts index eb43ba008..284ef3062 100644 --- a/packages/browser-wallet/src/shared/utils/types.ts +++ b/packages/browser-wallet/src/shared/utils/types.ts @@ -77,6 +77,7 @@ export type Cis2TransferParameters = [ ]; export type BackgroundSendTransactionPayload = { + payloadSponsored: string; accountAddress: string; type: AccountTransactionType; payload: string; From 50f295613d37c41da0ee8449b02d581b835c62ff Mon Sep 17 00:00:00 2001 From: Mani Singh Date: Mon, 15 Dec 2025 10:45:06 -0700 Subject: [PATCH 09/14] Sponsored Transaction UI updates --- .../SendSponsoredTransaction.scss | 30 ++++ .../SendSponsoredTransaction.tsx | 131 ++++++++++++++---- 2 files changed, 131 insertions(+), 30 deletions(-) diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss index 84247ba25..5347a4952 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.scss @@ -1,3 +1,33 @@ +.tooltip span span { + color: #000 !important; +} + +.tooltip span { + width: 263px !important; + word-break: break-word !important; + overflow-wrap: break-word !important; + white-space: pre-line !important; +} + +.free-transaction-btn { + box-sizing: border-box; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + gap: 6px; + width: 123px; + height: 24px; + border: 1px solid #b6c9e2; + border-radius: 9999px; + font-size: 12px; + cursor: pointer; + font-weight: 400; + margin-left: auto; + position: relative; +} + .send-transaction-x { .text__main { color: $color-mineral-2; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx index 25fb029fa..fce67c321 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/SendSponsoredTransaction.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect, useMemo } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { BackgroundSendTransactionPayload } from '@shared/utils/types'; import { useLocation } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; @@ -24,13 +24,9 @@ import { buildBasicAccountSigner, } from '@concordium/web-sdk'; import { Cbor, TokenOperationType } from '@concordium/web-sdk/plt'; -import { displayAsCcd, getPublicAccountAmounts } from 'wallet-common-helpers'; import { createPendingTransactionFromAccountTransaction, - getDefaultExpiry, - getTransactionAmount, getTransactionTypeName, - sendTransaction, } from '@popup/shared/utils/transaction-helpers'; import Page from '@popup/popupX/shared/Page'; import Text from '@popup/popupX/shared/Text'; @@ -41,6 +37,12 @@ import DisplayTransactionPayload, { DisplayParameters, } from '@popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload'; import DisplaySingleTransferTokenUpdate from '@popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate'; +import FullscreenNotice from '@popup/popupX/shared/FullscreenNotice'; +import Tooltip from '@popup/popupX/shared/Tooltip/Tooltip'; +import Info from '@assets/svgX/info.svg'; +import { LoaderInline } from '@popup/popupX/shared/Loader'; +import CheckCircle from '@assets/svgX/check-circle.svg'; +import Cross from '@assets/svgX/close.svg'; const isSingleTransferTokenUpdate = (transactionType: AccountTransactionType, payload: AccountTransactionPayload) => { const isTokenUpdate = transactionType === AccountTransactionType.TokenUpdate; @@ -59,11 +61,63 @@ interface Location { } interface Props { - onSubmit(hash: string): void; onReject(): void; } -export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) { +type TransactionStatusProps = { + success?: boolean | undefined; + amount?: string | number; + sponsoredAccount?: string; + tokenName?: string; +}; + +function TransactionStatus({ success, amount, sponsoredAccount, tokenName }: TransactionStatusProps) { + const { t } = useTranslation('x', { keyPrefix: 'submittedTransaction' }); + + let icon: JSX.Element; + switch (success) { + case undefined: { + icon = ; + break; + } + case true: { + icon = ; + break; + } + case false: { + icon = ; + break; + } + default: + throw new Error('Unexpected status'); + } + + return ( + <> + {icon} + Amount ({tokenName}) + {amount} + + + + Free Transaction + + + + + {success === true && {t('success.label')}} + {success === false && {t('failure.label')}} + {success === undefined && {t('pending.label')}} + + ); +} + +export default function SendSponsoredTransaction({ onReject }: Props) { const { state } = useLocation() as Location; const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); const addToast = useSetAtom(addToastAtom); @@ -72,7 +126,11 @@ export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) const addPendingTransaction = useUpdateAtom(addPendingTransactionAtom); const chainParameters = useBlockChainParameters(); - console.log('state', state); + const [showNoChanges, setShowNoChanges] = useState(false); + + const payloadSponsored = JSON.parse(state.payload.payloadSponsored); + const sponsoredAccount = payloadSponsored.header.sponsor.account; + const tokenSymbol = payloadSponsored.payload.tokenId; const { accountAddress, url } = state.payload; const key = usePrivateKey(accountAddress); @@ -114,14 +172,9 @@ export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) throw new Error(t('errors.missingKey')); } + setShowNoChanges(true); + const sender = AccountAddress.fromBase58(accountAddress); - const accountInfo = await client.getAccountInfo(sender); - // if ( - // getPublicAccountAmounts(accountInfo).atDisposal < - // getTransactionAmount(transactionType, payload) + (cost?.microCcdAmount || 0n) - // ) { - // throw new Error(t('errors.insufficientFunds')); - // } const nonce = await client.getNextAccountNonce(sender); @@ -129,14 +182,7 @@ export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) throw new Error(t('errors.missingNonce')); } - const header = { - expiry: getDefaultExpiry(), - sender, - nonce: nonce.nonce, - }; - // ToDo magic happens here - // @ts-ignore const transaction = parse(state.payload.payloadSponsored); const sponsoredTransaction = Transaction.signableFromJSON(transaction); const signed = await Transaction.signAndFinalize(sponsoredTransaction, buildBasicAccountSigner(key)); @@ -149,22 +195,39 @@ export default function SendSponsoredTransaction({ onSubmit, onReject }: Props) cost?.microCcdAmount ); await addPendingTransaction(pending); - return hash.toString(); }, [payload, key, cost]); const rejectHandler = withClose(onReject); + const signHandler = () => { handleSubmit() - .then(withClose(onSubmit)) + // .then(withClose(onSubmit)) .catch((e) => addToast(e.message)); }; + if (showNoChanges) { + return ( + setShowNoChanges(false)}> + + + + + + + ); + } + if (isSingleTransferTokenUpdate(transactionType, payload)) { return ( - SPONSORED PAGE - + + {t('payload.fee')} + + + Free Transaction + + + From a9a53d28f88d1b20cacf29e0eb1e92c1be231bac Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Fri, 9 Jan 2026 02:30:40 +0200 Subject: [PATCH 10/14] [WAL-1471] Add support for sponsored transactions Changed TokenHolder to CborAccountAddress Updated sponsored transactions UI according to design Fixed transaction handler functions Removed TS ignore --- examples/wallet-api/sign-token-update.html | 28 ++- .../wallet-api/sponsored-transaction.html | 1 - .../src/wallet-api-types.ts | 10 +- .../FullscreenPromptLayout.tsx | 2 +- .../popup/popupX/pages/SendFunds/Confirm.tsx | 4 +- .../popupX/pages/SendFunds/SendFunds.tsx | 4 +- .../DisplaySingleTransferTokenUpdate.tsx | 233 +++++++++++++++--- .../DisplayTransactionPayload.tsx | 231 ----------------- .../SendSponsoredTransaction.scss | 192 +++++---------- .../SendSponsoredTransaction.tsx | 183 ++++---------- .../SendSponsoredTransaction/i18n/en.ts | 54 ---- .../DisplaySingleTransferTokenUpdate.tsx | 12 +- .../SendTransaction/SendTransaction.scss | 30 ++- .../pages/prompts/SendTransaction/i18n/en.ts | 16 ++ .../src/popup/popupX/shared/Label/Label.scss | 4 + .../src/popup/popupX/shared/Label/Label.tsx | 5 +- .../popup/popupX/shared/Tooltip/Tooltip.scss | 2 + .../src/popup/shell/i18n/locales/en.ts | 2 - .../src/wallet-api/wallet-api.ts | 15 +- 19 files changed, 396 insertions(+), 632 deletions(-) delete mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplayTransactionPayload.tsx delete mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/i18n/en.ts diff --git a/examples/wallet-api/sign-token-update.html b/examples/wallet-api/sign-token-update.html index 8b55d33b6..f2fecefe4 100644 --- a/examples/wallet-api/sign-token-update.html +++ b/examples/wallet-api/sign-token-update.html @@ -30,8 +30,15 @@ }); document.getElementById('signSingleTransferTokenUpdate').addEventListener('click', () => { - const { TokenOperationType, Cbor, TokenAmount, TokenHolder, CborMemo, TokenId, AccountAddress } = - concordiumSDK; + const { + TokenOperationType, + Cbor, + TokenAmount, + CborAccountAddress, + CborMemo, + TokenId, + AccountAddress, + } = concordiumSDK; const ops = [ { @@ -40,7 +47,7 @@ value: '1000000', decimals: 6, }), - recipient: TokenHolder.fromAccountAddress( + recipient: CborAccountAddress.fromAccountAddress( AccountAddress.fromBase58('4PVJxxtiNVanfuNgvFWtFT1JCiqoVs6npVXBqwLNKprqnUiC2A') ), memo: CborMemo.fromString('Some text for memo'), @@ -62,8 +69,15 @@ }); document.getElementById('signTokenUpdate').addEventListener('click', () => { - const { TokenOperationType, Cbor, TokenAmount, TokenHolder, CborMemo, TokenId, AccountAddress } = - concordiumSDK; + const { + TokenOperationType, + Cbor, + TokenAmount, + CborAccountAddress, + CborMemo, + TokenId, + AccountAddress, + } = concordiumSDK; const ops = [ { @@ -72,7 +86,7 @@ value: '1000000', decimals: 6, }), - recipient: TokenHolder.fromAccountAddress( + recipient: CborAccountAddress.fromAccountAddress( AccountAddress.fromBase58('4PVJxxtiNVanfuNgvFWtFT1JCiqoVs6npVXBqwLNKprqnUiC2A') ), memo: CborMemo.fromString('Some text for memo'), @@ -95,7 +109,7 @@ value: '200000', decimals: 6, }), - recipient: TokenHolder.fromAccountAddress( + recipient: CborAccountAddress.fromAccountAddress( AccountAddress.fromBase58('4PVJxxtiNVanfuNgvFWtFT1JCiqoVs6npVXBqwLNKprqnUiC2A') ), memo: CborMemo.fromString('Some text for memo'), diff --git a/examples/wallet-api/sponsored-transaction.html b/examples/wallet-api/sponsored-transaction.html index 8ed02f45e..137cb58ad 100644 --- a/examples/wallet-api/sponsored-transaction.html +++ b/examples/wallet-api/sponsored-transaction.html @@ -81,7 +81,6 @@ const { AccountAddress, TokenOperationType, - TokenHolder, TokenAmount, CborMemo, Cbor, diff --git a/packages/browser-wallet-api-helpers/src/wallet-api-types.ts b/packages/browser-wallet-api-helpers/src/wallet-api-types.ts index 9bd942333..e0ff2e1ee 100644 --- a/packages/browser-wallet-api-helpers/src/wallet-api-types.ts +++ b/packages/browser-wallet-api-helpers/src/wallet-api-types.ts @@ -25,6 +25,7 @@ import type { UpdateContractInput, UpdateCredentialsInput, VerifiablePresentation, + Transaction, } from '@concordium/web-sdk'; import type { RpcTransport } from '@protobuf-ts/runtime-rpc'; import { LaxNumberEnumValue, LaxStringEnumValue } from './util'; @@ -69,6 +70,8 @@ export type SendTransactionPayload = | SendTransactionUpdateContractPayload | SendTransactionInitContractPayload; +export type SignableTransaction = Transaction.Signable; + export type SmartContractParameters = | { [key: string]: SmartContractParameters } | SmartContractParameters[] @@ -255,16 +258,17 @@ interface MainWalletApi { payload: ConfigureDelegationPayload ): Promise; /** - * Sends a transaction to the Concordium Wallet and awaits the users action. Note that a header is not sent, and will be constructed by the wallet itself. + * Sends a transaction signed by sponsor to the Concordium Wallet and awaits the users action. + * Note that a header is sent, and constructed by the sponsor. * Note that if the user rejects signing the transaction, this will throw an error. * @param accountAddress the address of the account that should sign the transaction * @param type the type of transaction that is to be signed and sent. - * @param payload the payload of the transaction to be signed and sent. Note that for smart contract transactions, the payload should not contain the parameters, those should instead be provided in the subsequent argument instead. + * @param payload the sponsored transaction with header to be signed and sent. */ sendSponsoredTransaction( accountAddress: AccountAddressSource, type: LaxNumberEnumValue, - payload: SimpleTransferPayload + payload: SignableTransaction ): Promise; /** * Sends a message to the Concordium Wallet and awaits the users action. If the user signs the message, this will resolve to the signature. diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx index 5ae39f883..c1da8704b 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx @@ -52,7 +52,7 @@ export default function FullscreenPromptLayout({ children }: { children: ReactNo (action) => (...args) => { action(...args); - // close(); + close(); }, [close] ); diff --git a/packages/browser-wallet/src/popup/popupX/pages/SendFunds/Confirm.tsx b/packages/browser-wallet/src/popup/popupX/pages/SendFunds/Confirm.tsx index cde31dccd..6d14ebd05 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/SendFunds/Confirm.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/SendFunds/Confirm.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Cbor, CborMemo, TokenAmount, TokenId, TokenOperationType, TokenHolder } from '@concordium/web-sdk/plt'; +import { Cbor, CborMemo, TokenAmount, TokenId, TokenOperationType, CborAccountAddress } from '@concordium/web-sdk/plt'; import { AccountAddress, AccountTransactionType, @@ -116,7 +116,7 @@ export default function SendFundsConfirm({ values, fee, sender }: Props) { value: parseTokenAmount(values.amount, tokenMetadata?.decimals).toString(), decimals: tokenMetadata?.decimals || 0, }), - recipient: TokenHolder.fromAccountAddress(receiver), + recipient: CborAccountAddress.fromAccountAddress(receiver), memo: values.memo ? CborMemo.fromString(values.memo) : undefined, }, }, diff --git a/packages/browser-wallet/src/popup/popupX/pages/SendFunds/SendFunds.tsx b/packages/browser-wallet/src/popup/popupX/pages/SendFunds/SendFunds.tsx index 9b4906454..8faa3e46c 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/SendFunds/SendFunds.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/SendFunds/SendFunds.tsx @@ -18,7 +18,7 @@ import { TokenId, TokenAmount as TokenAmountPlt, TokenOperationType, - TokenHolder, + CborAccountAddress, } from '@concordium/web-sdk/plt'; import { useAsyncMemo } from 'wallet-common-helpers'; import { useAtomValue } from 'jotai'; @@ -104,7 +104,7 @@ function SendFunds({ address }: SendFundsProps) { value: parseTokenAmount(amount, metadata?.decimals).toString(), decimals: metadata?.decimals || 0, }), - recipient: TokenHolder.fromAccountAddress(address), + recipient: CborAccountAddress.fromAccountAddress(address), memo: memo ? CborMemo.fromString(memo) : undefined, }, }, diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx index 258d94626..af4eb9e30 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendSponsoredTransaction/DisplaySingleTransferTokenUpdate.tsx @@ -1,19 +1,165 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { TokenUpdatePayload, AccountInfo } from '@concordium/web-sdk'; +import { + AccountInfo, + HexString, + isRejectTransaction, + isSuccessTransaction, + TokenUpdatePayload, + TransactionHash, + TransactionSummaryType, +} from '@concordium/web-sdk'; import { Cbor, CborMemo, TokenOperationType, TokenTransferOperation } from '@concordium/web-sdk/plt'; import { WalletCredential } from '@shared/storage/types'; import { displaySplitAddress, useCredential, useIdentityName } from '@popup/shared/utils/account-helpers'; -import Page from '@popup/popupX/shared/Page'; -import Text from '@popup/popupX/shared/Text'; -import Card from '@popup/popupX/shared/Card'; -import Button from '@popup/popupX/shared/Button'; import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext'; import { formatTokenAmount } from '@popup/popupX/shared/utils/helpers'; import { displayUrl } from '@popup/shared/utils/string-helpers'; import { logError } from '@shared/utils/log-helpers'; +import { LoaderInline } from '@popup/popupX/shared/Loader'; +import { fullscreenPromptContext } from '@popup/popupX/page-layouts/FullscreenPromptLayout'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { addToastAtom } from '@popup/state'; +import { grpcClientAtom } from '@popup/store/settings'; +import { useAsyncMemo } from 'wallet-common-helpers'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import Card from '@popup/popupX/shared/Card'; +import Button from '@popup/popupX/shared/Button'; +import Label from '@popup/popupX/shared/Label'; import Tooltip from '@popup/popupX/shared/Tooltip/Tooltip'; -import Info from '@assets/svgX/info.svg'; +import QuestionIcon from '@assets/svgX/UiKit/Interface/circled-question-mark.svg'; +import CheckCircle from '@assets/svgX/check-circle.svg'; +import Cross from '@assets/svgX/close.svg'; + +interface Status { + type?: 'success' | 'failure'; +} + +function TransactionStatusIcon({ status }: { status?: Status }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.sponsored' }); + switch (status?.type) { + case undefined: { + return ( + <> + + {t('pending.label')} + + ); + } + case 'success': { + return ( + <> + + {t('success.label')} + + ); + } + case 'failure': { + return ( + <> + + {t('failure.label')} + + ); + } + default: + throw new Error('Unexpected status'); + } +} + +type TransactionStatusProps = { + status?: Status; + amount?: string | number; + sponsorAccount?: string; + tokenName?: string; +}; + +function TransactionStatus({ status, amount, sponsorAccount, tokenName }: TransactionStatusProps) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.sponsored' }); + + return ( + <> + + {t('tokenNameAmount', { tokenName })} + {amount} + {t('transactionFee')}: +