diff --git a/.changeset/angry-beans-yawn.md b/.changeset/angry-beans-yawn.md new file mode 100644 index 00000000..8671abf6 --- /dev/null +++ b/.changeset/angry-beans-yawn.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat: tokens for enabled yields only option diff --git a/.changeset/clean-teeth-love.md b/.changeset/clean-teeth-love.md new file mode 100644 index 00000000..a5ad9fdf --- /dev/null +++ b/.changeset/clean-teeth-love.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat(externalProviders): add tron support diff --git a/.changeset/six-rats-return.md b/.changeset/six-rats-return.md new file mode 100644 index 00000000..52fe1a19 --- /dev/null +++ b/.changeset/six-rats-return.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat(externalProviders): display signTransaction message diff --git a/.changeset/sour-papers-grow.md b/.changeset/sour-papers-grow.md new file mode 100644 index 00000000..ef326a2b --- /dev/null +++ b/.changeset/sour-papers-grow.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat(externalProviders): provider details in txMeta diff --git a/.changeset/stupid-lights-taste.md b/.changeset/stupid-lights-taste.md new file mode 100644 index 00000000..488f5b9f --- /dev/null +++ b/.changeset/stupid-lights-taste.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +fix(bundledWidget): allow multiple re-renders diff --git a/.changeset/three-garlics-remain.md b/.changeset/three-garlics-remain.md new file mode 100644 index 00000000..fbd1f4a8 --- /dev/null +++ b/.changeset/three-garlics-remain.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat(tracking): add action steps cancelled event diff --git a/packages/widget/package.json b/packages/widget/package.json index 10b07041..0691df06 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -71,7 +71,7 @@ "@radix-ui/react-visually-hidden": "^1.1.3", "@safe-global/safe-apps-provider": "^0.18.6", "@safe-global/safe-apps-sdk": "^9.1.0", - "@stakekit/api-hooks": "0.0.98", + "@stakekit/api-hooks": "0.0.100", "@stakekit/common": "^0.0.48", "@stakekit/rainbowkit": "^2.2.4", "@tanstack/react-query": "^5.74.0", diff --git a/packages/widget/src/App.tsx b/packages/widget/src/App.tsx index 90649869..a29c2625 100644 --- a/packages/widget/src/App.tsx +++ b/packages/widget/src/App.tsx @@ -239,14 +239,14 @@ export const SKApp = (props: SKAppProps) => { }; export type BundledSKWidgetProps = SKAppProps & { - ref?: RefObject<{ rerender: (newProps: SKAppProps) => void }>; + ref?: RefObject<{ rerender: (newProps: BundledSKWidgetProps) => void }>; }; const BundledSKWidget = (_props: BundledSKWidgetProps) => { const [props, setProps] = useState(_props); useImperativeHandle(props.ref, () => ({ - rerender: (newProps: SKAppProps) => setProps(newProps), + rerender: (newProps: BundledSKWidgetProps) => setProps(newProps), })); return ; @@ -269,6 +269,7 @@ export const renderSKWidget = ({ root.render(); return { - rerender: (newProps: SKAppProps) => appRef.current.rerender(newProps), + rerender: (newProps: SKAppProps) => + appRef.current.rerender({ ...newProps, ref: appRef }), }; }; diff --git a/packages/widget/src/common/get-token-balances.ts b/packages/widget/src/common/get-token-balances.ts index 62a49125..a5ea8220 100644 --- a/packages/widget/src/common/get-token-balances.ts +++ b/packages/widget/src/common/get-token-balances.ts @@ -3,21 +3,28 @@ import { EitherAsync, Right } from "purify-ts"; import type { SKWallet } from "../domain/types"; import { getDefaultTokens } from "../hooks/api/use-default-tokens"; import { getTokenBalancesScan } from "../hooks/api/use-token-balances-scan"; +import type { SettingsProps } from "../providers/settings"; export const getTokenBalances = ({ additionalAddresses, address, network, queryClient, + tokensForEnabledYieldsOnly, }: { additionalAddresses: SKWallet["additionalAddresses"]; address: SKWallet["address"]; queryClient: QueryClient; network: SKWallet["network"]; + tokensForEnabledYieldsOnly: SettingsProps["tokensForEnabledYieldsOnly"]; }) => EitherAsync.fromPromise(() => Promise.all([ - getDefaultTokens({ queryClient, network }), + getDefaultTokens({ + queryClient, + network: network ?? undefined, + enabledYieldsOnly: tokensForEnabledYieldsOnly, + }), EitherAsync.liftEither( Right({ additionalAddresses, address, network }) ).chain(async (params) => { diff --git a/packages/widget/src/domain/types/chains.ts b/packages/widget/src/domain/types/chains.ts index da7e8e6f..462fba1a 100644 --- a/packages/widget/src/domain/types/chains.ts +++ b/packages/widget/src/domain/types/chains.ts @@ -124,6 +124,10 @@ export const isTonChain = (chain: string): chain is SupportedMiscChains => { return chain === MiscNetworks.Ton; }; +export const isTronChain = (chain: string): chain is SupportedMiscChains => { + return chain === MiscNetworks.Tron; +}; + export const isSupportedChain = (chain: string): chain is SupportedSKChains => { return ( isEvmChain(chain) || diff --git a/packages/widget/src/domain/types/external-providers.ts b/packages/widget/src/domain/types/external-providers.ts index 197fc728..6b9629a0 100644 --- a/packages/widget/src/domain/types/external-providers.ts +++ b/packages/widget/src/domain/types/external-providers.ts @@ -1,36 +1,33 @@ -import type { ActionDto, TransactionDto } from "@stakekit/api-hooks"; -import { EitherAsync, Left } from "purify-ts"; +import { EitherAsync, Left, Maybe, Right } from "purify-ts"; import type { RefObject } from "react"; import type { SKExternalProviders } from "./wallets"; -import type { SKTx } from "./wallets/generic-wallet"; +import type { SKTx, SKTxMeta } from "./wallets/generic-wallet"; export class ExternalProvider { constructor(private variantProvider: RefObject) {} - private invalidProviderType() { - return EitherAsync.liftEither(Left(new Error("Invalid provider type"))); - } - - sendTransaction( - tx: SKTx, - txMeta: { - txId: TransactionDto["id"]; - actionId: ActionDto["id"]; - actionType: ActionDto["type"]; - txType: TransactionDto["type"]; - } - ) { - const _sendTransaction = - this.variantProvider.current.provider.sendTransaction; + sendTransaction(tx: SKTx, txMeta: SKTxMeta) { + return EitherAsync.liftEither( + Maybe.fromNullable( + this.variantProvider.current.provider.sendTransaction + ).toEither(new Error("Invalid provider type")) + ) + .chain((_sendTransaction) => + EitherAsync(() => _sendTransaction(tx, txMeta)).mapLeft( + () => new Error("Failed to send transaction, unknown error") + ) + ) + .chain((res) => { + if (typeof res === "string") { + return EitherAsync.liftEither(Right(res)); + } - if (!_sendTransaction) { - return this.invalidProviderType(); - } + if (res.type === "success") { + return EitherAsync.liftEither(Right(res.txHash)); + } - return EitherAsync(() => _sendTransaction(tx, txMeta)).mapLeft((e) => { - console.log(e); - return new Error("Failed to send transaction"); - }); + return EitherAsync.liftEither(Left(res.error)); + }); } switchChain({ chainId }: { chainId: number }) { diff --git a/packages/widget/src/domain/types/wallet.ts b/packages/widget/src/domain/types/wallet.ts index 0f884fc0..6d390fa3 100644 --- a/packages/widget/src/domain/types/wallet.ts +++ b/packages/widget/src/domain/types/wallet.ts @@ -1,6 +1,7 @@ import type { Account } from "@ledgerhq/wallet-api-client"; +import type { SendTransactionError } from "@sk-widget/providers/sk-wallet/errors"; +import type { TransactionDecodeError } from "@sk-widget/providers/sk-wallet/errors"; import type { - ActionDto, AddressWithTokenDtoAdditionalAddresses, Networks, TransactionDto, @@ -8,12 +9,9 @@ import type { import type { EitherAsync } from "purify-ts"; import type { Chain } from "viem"; import type { Connector } from "wagmi"; -import type { - SendTransactionError, - TransactionDecodeError, -} from "../../pages/steps/hooks/errors"; import type { Nullable } from "../../types"; import type { SupportedSKChains } from "./chains"; +import type { SKTxMeta } from "./wallets/generic-wallet"; type SignedTxOrMessage = string; @@ -21,12 +19,7 @@ export type SKWallet = { disconnect: () => Promise; signTransaction: (args: { tx: NonNullable; - txMeta: { - txId: TransactionDto["id"]; - actionId: ActionDto["id"]; - actionType: ActionDto["type"]; - txType: TransactionDto["type"]; - }; + txMeta: SKTxMeta; ledgerHwAppId: Nullable; network: Networks; }) => EitherAsync< diff --git a/packages/widget/src/domain/types/wallets/generic-wallet.ts b/packages/widget/src/domain/types/wallets/generic-wallet.ts index 8855f7cf..a60496a1 100644 --- a/packages/widget/src/domain/types/wallets/generic-wallet.ts +++ b/packages/widget/src/domain/types/wallets/generic-wallet.ts @@ -1,8 +1,11 @@ -import type { ActionDto, TransactionDto } from "@stakekit/api-hooks"; +import type { + ActionDto, + RewardTypes, + TransactionDto, +} from "@stakekit/api-hooks"; +import type * as TronWeb from "tronweb"; import type { Hex } from "viem"; -type Base64String = string; - export enum TxType { Legacy = "0x1", EIP1559 = "0x2", @@ -29,17 +32,42 @@ export type EVMTx = { ); }; -export type SolanaTx = { type: "solana"; tx: Base64String }; +export type SolanaTx = { type: "solana"; tx: string }; export type TonTx = { type: "ton"; tx: { seqno: bigint; - message: Base64String; + message: string; }; }; -export type SKTx = EVMTx | SolanaTx | TonTx; +export type TronTx = { + type: "tron"; + tx: TronWeb.Types.Transaction; +}; + +export type SKTx = EVMTx | SolanaTx | TonTx | TronTx; + +export type ActionMeta = { + actionId: ActionDto["id"]; + actionType: ActionDto["type"]; + amount: ActionDto["amount"]; + inputToken: ActionDto["inputToken"]; + providersDetails: { + name: string; + address: string | undefined; + rewardRate: number | undefined; + rewardType: RewardTypes; + website: string | undefined; + logo: string | undefined; + }[]; +}; + +export type SKTxMeta = ActionMeta & { + txId: TransactionDto["id"]; + txType: TransactionDto["type"]; +}; export type SKWallet = { signMessage: (message: string) => Promise; @@ -47,11 +75,10 @@ export type SKWallet = { getTransactionReceipt?(txHash: string): Promise<{ transactionHash?: string }>; sendTransaction( tx: SKTx, - txMeta: { - txId: TransactionDto["id"]; - actionId: ActionDto["id"]; - actionType: ActionDto["type"]; - txType: TransactionDto["type"]; - } - ): Promise; + txMeta: SKTxMeta + ): Promise< + | string + | { type: "success"; txHash: string } + | { type: "error"; error: string } + >; }; diff --git a/packages/widget/src/hooks/api/use-default-tokens.ts b/packages/widget/src/hooks/api/use-default-tokens.ts index 5ea65e73..f07404d4 100644 --- a/packages/widget/src/hooks/api/use-default-tokens.ts +++ b/packages/widget/src/hooks/api/use-default-tokens.ts @@ -1,17 +1,27 @@ -import type { TokenBalanceScanResponseDto } from "@stakekit/api-hooks"; +import { useSettings } from "@sk-widget/providers/settings"; +import type { + TokenBalanceScanResponseDto, + TokenGetTokensParams, +} from "@stakekit/api-hooks"; import { getTokenGetTokensQueryKey, tokenGetTokens } from "@stakekit/api-hooks"; import type { QueryClient } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query"; import { EitherAsync } from "purify-ts"; -import type { SKWallet } from "../../domain/types"; import { useSKWallet } from "../../providers/sk-wallet"; export const useDefaultTokens = () => { const { network } = useSKWallet(); + const { tokensForEnabledYieldsOnly } = useSettings(); return useQuery({ queryKey: getTokenGetTokensQueryKey({ network: network ?? undefined }), - queryFn: async () => (await queryFn({ network })).unsafeCoerce(), + queryFn: async () => + ( + await queryFn({ + network: network ?? undefined, + enabledYieldsOnly: !!tokensForEnabledYieldsOnly, + }) + ).unsafeCoerce(), staleTime: 1000 * 60 * 5, }); }; @@ -33,10 +43,13 @@ export const getDefaultTokens = ( const queryFn = ({ network, -}: { - network: SKWallet["network"]; -}) => - EitherAsync(() => tokenGetTokens({ network: network ?? undefined })).map( - (val) => - val.map((v) => ({ ...v, amount: "0" })) + enabledYieldsOnly, +}: Pick) => + EitherAsync(() => + tokenGetTokens({ + network, + enabledYieldsOnly: enabledYieldsOnly || undefined, + }) + ).map((val) => + val.map((v) => ({ ...v, amount: "0" })) ); diff --git a/packages/widget/src/pages/details/earn-page/state/use-init-token.ts b/packages/widget/src/pages/details/earn-page/state/use-init-token.ts index 9313d355..1a21dff8 100644 --- a/packages/widget/src/pages/details/earn-page/state/use-init-token.ts +++ b/packages/widget/src/pages/details/earn-page/state/use-init-token.ts @@ -23,7 +23,7 @@ export const useInitToken = () => { const queryClient = useSKQueryClient(); const { data: positionsData } = usePositionsData(); - const { externalProviders } = useSettings(); + const { externalProviders, tokensForEnabledYieldsOnly } = useSettings(); return useQuery({ staleTime: Number.POSITIVE_INFINITY, @@ -41,6 +41,7 @@ export const useInitToken = () => { address, network, queryClient, + tokensForEnabledYieldsOnly, }).chain((val) => getInitParams({ isLedgerLive, diff --git a/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts b/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts index eba02730..f02e0044 100644 --- a/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts +++ b/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts @@ -21,7 +21,7 @@ export const useInitYield = ({ const { isLedgerLive, isConnected, network, additionalAddresses, address } = useSKWallet(); const queryClient = useSKQueryClient(); - const { externalProviders } = useSettings(); + const { externalProviders, tokensForEnabledYieldsOnly } = useSettings(); const { data: positionsData } = usePositionsData(); return useQuery({ @@ -44,6 +44,7 @@ export const useInitYield = ({ address, network, queryClient, + tokensForEnabledYieldsOnly, }) .chain((val) => EitherAsync.liftEither( diff --git a/packages/widget/src/pages/steps/hooks/errors.ts b/packages/widget/src/pages/steps/hooks/errors.ts index 4f80c7c9..221529ec 100644 --- a/packages/widget/src/pages/steps/hooks/errors.ts +++ b/packages/widget/src/pages/steps/hooks/errors.ts @@ -1,16 +1,12 @@ -export class SendTransactionError extends Error { - name = "SendTransactionError"; -} -export class NotSupportedFlowError extends Error { - name = "NotSupportedFlowError"; -} export class SignError extends Error { + _tag = "SignError"; txId: string; network: string; - name = "SignError"; constructor({ network, txId }: { txId: string; network: string }) { super(); + + this._tag = "SignError"; this.txId = txId; this.network = network; } @@ -19,14 +15,16 @@ export class GetStakeSessionError extends Error { name = "GetStakeSessionError"; } export class TransactionConstructError extends Error { - name = "TransactionConstructError"; + _tag = "TransactionConstructError"; + + constructor(message?: string) { + super(message); + this._tag = "TransactionConstructError"; + } } export class TXCheckError extends Error { name = "TXCheckError"; } -export class TransactionDecodeError extends Error { - name = "TransactionDecodeError"; -} export class SubmitHashError extends Error { name = "SubmitHashError"; } diff --git a/packages/widget/src/pages/steps/hooks/use-steps-machine.hook.ts b/packages/widget/src/pages/steps/hooks/use-steps-machine.hook.ts index d61ddce7..fdd30135 100644 --- a/packages/widget/src/pages/steps/hooks/use-steps-machine.hook.ts +++ b/packages/widget/src/pages/steps/hooks/use-steps-machine.hook.ts @@ -1,4 +1,9 @@ +import type { ActionMeta } from "@sk-widget/domain/types/wallets/generic-wallet"; import { useSavedRef } from "@sk-widget/hooks"; +import type { + SendTransactionError, + TransactionDecodeError, +} from "@sk-widget/providers/sk-wallet/errors"; import type { ActionDto, TransactionDto } from "@stakekit/api-hooks"; import { transactionConstruct, @@ -17,7 +22,7 @@ import { withRequestErrorRetry } from "../../../common/utils"; import { isTxError } from "../../../domain"; import { useTrackEvent } from "../../../hooks/tracking/use-track-event"; import { useSKWallet } from "../../../providers/sk-wallet"; -import type { GetStakeSessionError, SendTransactionError } from "./errors"; +import type { GetStakeSessionError } from "./errors"; import { SignError, SubmitError, @@ -31,10 +36,9 @@ type TxMeta = { signedTx: string | null; broadcasted: boolean | null; signError: - | Error - | GetStakeSessionError | SendTransactionError - | SignError + | TransactionDecodeError + | TransactionConstructError | null; txCheckError: GetStakeSessionError | null; done: boolean; @@ -58,13 +62,11 @@ type SignRes = export const useStepsMachine = ({ transactions, integrationId, - actionId, - actionType, + actionMeta, }: { transactions: ActionDto["transactions"]; integrationId: ActionDto["integrationId"]; - actionId: ActionDto["id"]; - actionType: ActionDto["type"]; + actionMeta: ActionMeta; }) => { const { signTransaction, signMessage, isLedgerLive } = useSKWallet(); @@ -82,8 +84,7 @@ export const useStepsMachine = ({ trackEvent, signMessage, signTransaction, - actionId, - actionType, + actionMeta, }); return useMachine(useState(() => getMachine(machineParams))[0]); @@ -98,8 +99,7 @@ const getMachine = ( trackEvent: ReturnType; signMessage: ReturnType["signMessage"]; signTransaction: ReturnType["signTransaction"]; - actionId: ActionDto["id"]; - actionType: ActionDto["type"]; + actionMeta: ActionMeta; }> > ) => { @@ -130,7 +130,14 @@ const getMachine = ( type: "__SIGN_SUCCESS__"; val: Extract; } - | { type: "__SIGN_ERROR__"; val: Error } + | { + type: "__SIGN_ERROR__"; + val: + | SendTransactionError + | TransactionDecodeError + | TransactionConstructError + | SignError; + } | { type: "__BROADCAST_SUCCESS__" } | { type: "__BROADCAST_ERROR__"; val: Error | SubmitHashError } | { type: "__DONE__"; val: TxState[] } @@ -223,15 +230,16 @@ const getMachine = ( EitherAsync.liftEither( context.currentTxMeta .chainNullable((v) => context.txStates[v.idx].tx) - .toEither(new Error("missing tx")) + .toEither(new TransactionConstructError("missing tx")) ) - .chain((tx) => { - /** - * Single sign transactions - */ - return getAverageGasMode({ - network: tx.network, - }) + .chain< + | TransactionConstructError + | SendTransactionError + | TransactionDecodeError + | SignError, + SignRes + >((tx) => + getAverageGasMode({ network: tx.network }) .chainLeft(async () => Right(null)) .chain((gas) => txConstruct(tx.id, { @@ -239,7 +247,13 @@ const getMachine = ( ledgerWalletAPICompatible: ref.current.isLedgerLive, }).mapLeft(() => new TransactionConstructError()) ) - .chain((constructedTx) => { + .chain< + | TransactionConstructError + | SendTransactionError + | TransactionDecodeError + | SignError, + SignRes + >((constructedTx) => { if ( constructedTx.status === "BROADCASTED" || constructedTx.status === "CONFIRMED" @@ -259,9 +273,16 @@ const getMachine = ( return ref.current .signMessage(constructedTx.unsignedTransaction) .map((val) => ({ - type: "regular", + type: "regular" as const, data: { signedTx: val, broadcasted: false }, - })); + })) + .mapLeft( + () => + new SignError({ + network: constructedTx.network, + txId: constructedTx.id, + }) + ); } return ref.current @@ -269,9 +290,8 @@ const getMachine = ( tx: constructedTx.unsignedTransaction, ledgerHwAppId: constructedTx.ledgerHwAppId, txMeta: { - actionId: ref.current.actionId, + ...ref.current.actionMeta, txId: constructedTx.id, - actionType: ref.current.actionType, txType: constructedTx.type, }, network: constructedTx.network, @@ -289,8 +309,8 @@ const getMachine = ( }) ) .map((val) => ({ type: "regular", data: val })); - }); - }) + }) + ) .caseOf({ Left: (l) => { console.log(l); diff --git a/packages/widget/src/pages/steps/hooks/use-steps.hook.ts b/packages/widget/src/pages/steps/hooks/use-steps.hook.ts index ed8dfec5..62ac90d7 100644 --- a/packages/widget/src/pages/steps/hooks/use-steps.hook.ts +++ b/packages/widget/src/pages/steps/hooks/use-steps.hook.ts @@ -1,3 +1,6 @@ +import type { ActionMeta } from "@sk-widget/domain/types/wallets/generic-wallet"; +import { useTrackEvent } from "@sk-widget/hooks/tracking/use-track-event"; +import type { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import { useSetActionHistoryData } from "@sk-widget/providers/stake-history"; import type { ActionDto, TransactionType } from "@stakekit/api-hooks"; import { useEffect, useLayoutEffect, useMemo } from "react"; @@ -13,19 +16,42 @@ import { useStepsMachine } from "./use-steps-machine.hook"; export const useSteps = ({ session, onSignSuccess, + providersDetails, }: { onSignSuccess?: () => void; session: ActionDto; + providersDetails: ReturnType; }) => { const navigate = useNavigate(); const callbacksRef = useSavedRef({ onSignSuccess }); + const actionMeta = useMemo( + (): ActionMeta => ({ + actionId: session.id, + actionType: session.type, + amount: session.amount, + inputToken: session.inputToken, + providersDetails: providersDetails + .map((providerDetail) => + providerDetail.map((v) => ({ + name: v.name, + address: v.address, + rewardRate: v.rewardRate, + rewardType: v.rewardType, + website: v.website, + logo: v.logo, + })) + ) + .orDefault([]), + }), + [session, providersDetails] + ); + const [machineState, send, actorRef] = useStepsMachine({ transactions: session.transactions, integrationId: session.integrationId, - actionId: session.id, - actionType: session.type, + actionMeta, }); /** @@ -95,7 +121,12 @@ export const useSteps = ({ machineState.status, ]); - const onClick = () => navigate(-1); + const trackEvent = useTrackEvent(); + + const onClick = () => { + trackEvent("actionStepsCancelled"); + navigate(-1); + }; const retry = (() => { if (machineState.matches("signError")) { diff --git a/packages/widget/src/pages/steps/pages/activity-steps.page.tsx b/packages/widget/src/pages/steps/pages/activity-steps.page.tsx index 9c624950..581e7126 100644 --- a/packages/widget/src/pages/steps/pages/activity-steps.page.tsx +++ b/packages/widget/src/pages/steps/pages/activity-steps.page.tsx @@ -1,15 +1,35 @@ +import { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import { useActivityContext } from "@sk-widget/providers/activity-provider"; import { useSelector } from "@xstate/store/react"; +import { Maybe } from "purify-ts"; +import { useMemo } from "react"; import { useTrackPage } from "../../../hooks/tracking/use-track-page"; import { StepsPage } from "./common.page"; export const ActivityStepsPage = () => { useTrackPage("activitySteps"); + const activityContext = useActivityContext(); + const selectedAction = useSelector( - useActivityContext(), + activityContext, (state) => state.context.selectedAction ).unsafeCoerce(); - return ; + const selectedYield = useSelector( + activityContext, + (state) => state.context.selectedYield + ).unsafeCoerce(); + + const providersDetails = useProvidersDetails({ + integrationData: useMemo(() => Maybe.of(selectedYield), [selectedYield]), + validatorsAddresses: useMemo( + () => Maybe.of(selectedAction.validatorAddresses ?? []), + [selectedAction.validatorAddresses] + ), + }); + + return ( + + ); }; diff --git a/packages/widget/src/pages/steps/pages/common.page.tsx b/packages/widget/src/pages/steps/pages/common.page.tsx index e6950458..04fbd526 100644 --- a/packages/widget/src/pages/steps/pages/common.page.tsx +++ b/packages/widget/src/pages/steps/pages/common.page.tsx @@ -1,3 +1,4 @@ +import type { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import type { ActionDto } from "@stakekit/api-hooks"; import { motion } from "motion/react"; import { useTranslation } from "react-i18next"; @@ -10,12 +11,18 @@ import { TxState } from "./tx-state"; type StepsPageProps = { session: ActionDto; onSignSuccess?: () => void; + providersDetails: ReturnType; }; -export const StepsPage = ({ session, onSignSuccess }: StepsPageProps) => { +export const StepsPage = ({ + session, + onSignSuccess, + providersDetails, +}: StepsPageProps) => { const { retry, txStates } = useSteps({ session, onSignSuccess, + providersDetails, }); const { t } = useTranslation(); diff --git a/packages/widget/src/pages/steps/pages/pending-steps.page.tsx b/packages/widget/src/pages/steps/pages/pending-steps.page.tsx index 7383dc17..c0d4b660 100644 --- a/packages/widget/src/pages/steps/pages/pending-steps.page.tsx +++ b/packages/widget/src/pages/steps/pages/pending-steps.page.tsx @@ -1,15 +1,42 @@ +import { useUnstakeOrPendingActionParams } from "@sk-widget/hooks/navigation/use-unstake-or-pending-action-params"; +import { usePositionBalances } from "@sk-widget/hooks/use-position-balances"; +import { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import { usePendingActionStore } from "@sk-widget/providers/pending-action-store"; import { useSelector } from "@xstate/store/react"; +import { Maybe } from "purify-ts"; +import { useMemo } from "react"; import { useTrackPage } from "../../../hooks/tracking/use-track-page"; import { StepsPage } from "./common.page"; export const PendingStepsPage = () => { + useTrackPage("pendingActionSteps"); + const pendingRequest = useSelector( usePendingActionStore(), (state) => state.context.data ).unsafeCoerce(); - useTrackPage("pendingActionSteps"); + const { plain } = useUnstakeOrPendingActionParams(); + + const positionBalances = usePositionBalances({ + balanceId: plain.balanceId, + integrationId: plain.integrationId, + }); + + const providersDetails = useProvidersDetails({ + integrationData: useMemo( + () => Maybe.of(pendingRequest.integrationData), + [pendingRequest.integrationData] + ), + validatorsAddresses: positionBalances.data.map((p) => + p.type === "validators" ? p.validatorsAddresses : [] + ), + }); - return ; + return ( + + ); }; diff --git a/packages/widget/src/pages/steps/pages/stake-steps.page.tsx b/packages/widget/src/pages/steps/pages/stake-steps.page.tsx index e037e0fa..e170726c 100644 --- a/packages/widget/src/pages/steps/pages/stake-steps.page.tsx +++ b/packages/widget/src/pages/steps/pages/stake-steps.page.tsx @@ -1,6 +1,8 @@ +import { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import { useEnterStakeStore } from "@sk-widget/providers/enter-stake-store"; import { useSelector } from "@xstate/store/react"; import { Maybe } from "purify-ts"; +import { useMemo } from "react"; import { importValidator } from "../../../common/import-validator"; import { useTrackPage } from "../../../hooks/tracking/use-track-page"; import { useSKWallet } from "../../../providers/sk-wallet"; @@ -33,10 +35,22 @@ export const StakeStepsPage = () => { ) ); + const providersDetails = useProvidersDetails({ + integrationData: useMemo( + () => Maybe.of(enterRequest.selectedStake), + [enterRequest.selectedStake] + ), + validatorsAddresses: useMemo( + () => Maybe.of(enterRequest.selectedValidators), + [enterRequest.selectedValidators] + ), + }); + return ( ); }; diff --git a/packages/widget/src/pages/steps/pages/tx-state.tsx b/packages/widget/src/pages/steps/pages/tx-state.tsx index d67ed191..a2119a16 100644 --- a/packages/widget/src/pages/steps/pages/tx-state.tsx +++ b/packages/widget/src/pages/steps/pages/tx-state.tsx @@ -119,7 +119,7 @@ export const TxState = ({ txState, position, count }: Props) => { {t("steps.approve")} {txState.state === TxStateEnum.SIGN_ERROR ? ( - {t("steps.approve_error")} + {txState.meta.signError?.message || t("steps.approve_error")} ) : ( diff --git a/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx b/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx index a41c2ebf..b48a4f87 100644 --- a/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx +++ b/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx @@ -1,15 +1,41 @@ +import { useUnstakeOrPendingActionParams } from "@sk-widget/hooks/navigation/use-unstake-or-pending-action-params"; +import { usePositionBalances } from "@sk-widget/hooks/use-position-balances"; +import { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import { useExitStakeStore } from "@sk-widget/providers/exit-stake-store"; import { useSelector } from "@xstate/store/react"; +import { Maybe } from "purify-ts"; +import { useMemo } from "react"; import { useTrackPage } from "../../../hooks/tracking/use-track-page"; import { StepsPage } from "./common.page"; export const UnstakeStepsPage = () => { + useTrackPage("unstakeSteps"); + const exitRequest = useSelector( useExitStakeStore(), (state) => state.context.data ).unsafeCoerce(); - useTrackPage("unstakeSteps"); + const { plain } = useUnstakeOrPendingActionParams(); + const positionBalances = usePositionBalances({ + balanceId: plain.balanceId, + integrationId: plain.integrationId, + }); + + const providersDetails = useProvidersDetails({ + integrationData: useMemo( + () => Maybe.of(exitRequest.integrationData), + [exitRequest.integrationData] + ), + validatorsAddresses: positionBalances.data.map((p) => + p.type === "validators" ? p.validatorsAddresses : [] + ), + }); - return ; + return ( + + ); }; diff --git a/packages/widget/src/providers/settings.tsx b/packages/widget/src/providers/settings.tsx index 366c60b7..35c80387 100644 --- a/packages/widget/src/providers/settings.tsx +++ b/packages/widget/src/providers/settings.tsx @@ -44,6 +44,7 @@ export type SettingsProps = { disableInjectedProviderDiscovery?: boolean; mapWalletFn?: Parameters[0]["mapWalletFn"]; customTranslations?: RecursivePartial; + tokensForEnabledYieldsOnly?: boolean; }; export type SettingsContextType = SettingsProps & VariantProps; diff --git a/packages/widget/src/providers/sk-wallet/errors.ts b/packages/widget/src/providers/sk-wallet/errors.ts index 67868bc6..2418f6b6 100644 --- a/packages/widget/src/providers/sk-wallet/errors.ts +++ b/packages/widget/src/providers/sk-wallet/errors.ts @@ -7,3 +7,24 @@ export class SafeFailedError extends Error { this.type = type; } } + +export class SendTransactionError extends Error { + _tag = "SendTransactionError"; + + constructor(message?: string) { + super(message); + + this._tag = "SendTransactionError"; + } +} +export class TransactionDecodeError extends Error { + _tag = "TransactionDecodeError"; + + constructor(message?: string) { + super(message); + + this._tag = "TransactionDecodeError"; + } +} + +new SendTransactionError(); diff --git a/packages/widget/src/providers/sk-wallet/index.tsx b/packages/widget/src/providers/sk-wallet/index.tsx index a8316cd5..18f85875 100644 --- a/packages/widget/src/providers/sk-wallet/index.tsx +++ b/packages/widget/src/providers/sk-wallet/index.tsx @@ -5,11 +5,18 @@ import { isEvmChain, isSolanaChain, isTonChain, + isTronChain, } from "@sk-widget/domain/types/chains"; -import type { SKTx } from "@sk-widget/domain/types/wallets/generic-wallet"; +import type { + SKTx, + TronTx, +} from "@sk-widget/domain/types/wallets/generic-wallet"; import { useCheckIsUnmounted } from "@sk-widget/hooks/use-check-is-unmounted"; import { isSafeConnector } from "@sk-widget/providers/safe/safe-connector-meta"; -import { SafeFailedError } from "@sk-widget/providers/sk-wallet/errors"; +import { + SafeFailedError, + SendTransactionError, +} from "@sk-widget/providers/sk-wallet/errors"; import { useInit } from "@sk-widget/providers/sk-wallet/use-init"; import { Either, EitherAsync, Left, Maybe, Right } from "purify-ts"; import type { PropsWithChildren } from "react"; @@ -30,17 +37,13 @@ import { import type { SKWallet } from "../../domain/types"; import { useTrackEvent } from "../../hooks/tracking/use-track-event"; import { useIsomorphicEffect } from "../../hooks/use-isomorphic-effect"; -import { - type NotSupportedFlowError, - SendTransactionError, - TransactionDecodeError, -} from "../../pages/steps/hooks/errors"; import { isLedgerDappBrowserProvider } from "../../utils"; import { isCosmosConnector } from "../cosmos/cosmos-connector-meta"; import { isExternalProviderConnector } from "../external-provider"; import { isLedgerLiveConnector } from "../ledger/ledger-live-connector-meta"; import { isTronConnector } from "../misc/tron-connector-meta"; import { useWagmiConfig } from "../wagmi"; +import { TransactionDecodeError } from "./errors"; import { useAdditionalAddresses } from "./use-additional-addresses"; import { useConnectorChains } from "./use-connector-chains"; import { useCosmosCW } from "./use-cosmos-cw"; @@ -163,215 +166,239 @@ export const SKWalletProvider = ({ children }: PropsWithChildren) => { const signTransaction = useCallback( ({ tx, ledgerHwAppId, txMeta, network }) => - connectorDetails.chain< - TransactionDecodeError | SendTransactionError | NotSupportedFlowError, - { signedTx: string; broadcasted: boolean } - >(({ conn, address }) => { - /** - * Ledger Live connector - */ - if (isLedgerLiveConnector(conn)) { - return EitherAsync.liftEither( - Maybe.fromNullable(ledgerCurrentAccountId).toEither( - new Error("currentAccountId missing") + connectorDetails + .mapLeft(() => new SendTransactionError()) + .chain< + TransactionDecodeError | SendTransactionError, + { signedTx: string; broadcasted: boolean } + >(({ conn, address }) => { + /** + * Ledger Live connector + */ + if (isLedgerLiveConnector(conn)) { + return EitherAsync.liftEither( + Maybe.fromNullable(ledgerCurrentAccountId).toEither( + new SendTransactionError() + ) ) - ) - .chain((val) => - EitherAsync.liftEither( - Either.encase(() => JSON.parse(tx)) - .mapLeft(() => new Error("JSON.parse failed")) - .chain((parsedTx) => - Either.encase(() => - conn.deserializeTransaction(parsedTx) - ).mapLeft(() => new Error("deserializeTransaction failed")) + .chain((val) => + EitherAsync.liftEither( + Either.encase(() => JSON.parse(tx)) + + .chain((parsedTx) => + Either.encase(() => conn.deserializeTransaction(parsedTx)) + ) + .mapLeft(() => new TransactionDecodeError()) + ).map((deserializedTransaction) => ({ + accountId: val, + deserializedTransaction, + })) + ) + .chain(({ accountId, deserializedTransaction }) => + EitherAsync(() => + conn.walletApiClient.transaction.signAndBroadcast( + accountId, + deserializedTransaction, + Maybe.fromNullable(ledgerHwAppId) + .map((v) => ({ hwAppId: v })) + .extract() ) - ).map((deserializedTransaction) => ({ - accountId: val, - deserializedTransaction, - })) + ).mapLeft((e) => { + console.log(e); + return new SendTransactionError(); + }) + ) + .map((val) => ({ signedTx: val, broadcasted: true })); + } + + /** + * Cosmos connector + */ + if (isCosmosConnector(conn)) { + return EitherAsync.liftEither( + Maybe.fromNullable(cosmosCW).toEither( + new Error("cosmosCW missing") + ) ) - .chain(({ accountId, deserializedTransaction }) => - EitherAsync(() => - conn.walletApiClient.transaction.signAndBroadcast( - accountId, - deserializedTransaction, - Maybe.fromNullable(ledgerHwAppId) - .map((v) => ({ hwAppId: v })) - .extract() - ) - ).mapLeft((e) => { - console.log(e); - return new Error("sign failed"); - }) + .chain((cw) => + // We need to sign + broadcast as `walletconnect` cosmos client does not support `sendTx` + conn + .signTransaction({ cw, tx }) + .map((val) => ({ signedTx: val, broadcasted: false })) + ) + .mapLeft(() => new SendTransactionError()); + } + + /** + * Tron connector + */ + if (isTronConnector(conn)) { + return EitherAsync.liftEither( + Either.encase(() => JSON.parse(tx)) + .chain((val) => unsignedTronTransactionCodec.decode(val)) + .mapLeft((e) => { + console.log(e); + return new TransactionDecodeError(); + }) ) - .map((val) => ({ signedTx: val, broadcasted: true })); - } + .chain((val) => + EitherAsync(() => conn.signTransaction(val)).mapLeft((e) => { + console.log(e); + return new SendTransactionError(); + }) + ) + .map((val) => ({ + signedTx: JSON.stringify(val), + broadcasted: false, + })); + } - /** - * Cosmos connector - */ - if (isCosmosConnector(conn)) { - return EitherAsync.liftEither( - Maybe.fromNullable(cosmosCW).toEither(new Error("cosmosCW missing")) - ).chain((cw) => - // We need to sign + broadcast as `walletconnect` cosmos client does not support `sendTx` - conn - .signTransaction({ cw, tx }) - .map((val) => ({ signedTx: val, broadcasted: false })) - ); - } + /** + * External provider connector + */ + if (isExternalProviderConnector(conn)) { + return EitherAsync.liftEither( + Right(null) + .chain(() => { + if (isEvmChain(network)) { + return Either.encase(() => JSON.parse(tx)) + .mapLeft(() => "Failed to parse tx") + .chain((val) => unsignedEVMTransactionCodec.decode(val)) + .map((v) => prepareEVMTx({ address, decodedTx: v })); + } + + if (isSolanaChain(network)) { + return unsignedSolanaTransactionCodec + .decode(tx) + .map((v) => ({ type: "solana", tx: v })); + } + + if (isTonChain(network)) { + return Either.encase(() => JSON.parse(tx)) + .mapLeft(() => "Failed to parse tx") + .chain((val) => unsignedTonTransactionCodec.decode(val)) + .map((v) => ({ type: "ton", tx: v })); + } + + if (isTronChain(network)) { + return Either.encase(() => JSON.parse(tx)) + .mapLeft(() => "Failed to parse tx") + .chain((val) => unsignedTronTransactionCodec.decode(val)) + .map((v) => ({ type: "tron", tx: v }) as TronTx); + } + + return Left("Unsupported network"); + }) + .mapLeft((e) => { + console.log(e); + return new TransactionDecodeError(); + }) + ) + .chain((val) => + conn + .sendTransaction(val, txMeta) + .mapLeft( + (e) => + new SendTransactionError( + typeof e === "string" ? e : undefined + ) + ) + ) + .map((val) => ({ signedTx: val, broadcasted: true })); + } - /** - * Tron connector - */ - if (isTronConnector(conn)) { - return EitherAsync.liftEither( - Either.encase(() => JSON.parse(tx)) - .chain((val) => unsignedTronTransactionCodec.decode(val)) - .mapLeft((e) => { - console.log(e); - return new TransactionDecodeError(); - }) - ) - .chain((val) => - EitherAsync(() => conn.signTransaction(val)).mapLeft((e) => { - console.log(e); - return new Error("sign failed"); - }) + /** + * Safe connector + */ + if (isSafeConnector(conn)) { + return EitherAsync.liftEither( + Either.encase(() => JSON.parse(tx)) + .chain((val) => unsignedEVMTransactionCodec.decode(val)) + .map((val) => prepareEVMTx({ address, decodedTx: val })) + .mapLeft(() => new TransactionDecodeError()) ) - .map((val) => ({ - signedTx: JSON.stringify(val), - broadcasted: false, - })); - } - - /** - * External provider connector - */ - if (isExternalProviderConnector(conn)) { + .chain(({ tx }) => + conn + .sendTransactions({ + txs: [ + { + data: tx.data, + to: tx.to, + value: tx.value ?? "0", + }, + ], + }) + .map((res) => res.safeTxHash) + ) + .chain((safeTxHash) => + withRequestErrorRetry({ + fn: () => + conn + .getTxStatus(safeTxHash) + .chain((res) => + !res.txHash || res.txStatus !== conn.txStatus.SUCCESS + ? EitherAsync.liftEither( + Left( + new SafeFailedError( + res.txStatus === conn.txStatus.FAILED || + res.txStatus === conn.txStatus.CANCELLED + ? "FAILED" + : "NOT_READY" + ) + ) + ) + : EitherAsync.liftEither(Right(res.txHash)) + ) + .run() + .then((res) => res.unsafeCoerce()), + shouldRetry: (error, retryCount) => + Maybe.fromNullable(error) + .chainNullable((e) => + (e as SafeFailedError)._tag === "SafeFailedError" + ? (e as SafeFailedError) + : null + ) + .filter((e) => e.type !== "FAILED" && !checkIsUnmounted()) + .map(() => retryCount < 120) + .orDefault(false), + retryWaitForMs: () => 7000, + }) + ) + .mapLeft(() => new SendTransactionError()) + .map((val) => ({ signedTx: val as Hash, broadcasted: true })); + } + + /** + * EVM connector + */ return EitherAsync.liftEither( - Right(null) - .chain(() => { - if (isEvmChain(network)) { - return Either.encase(() => JSON.parse(tx)) - .mapLeft(() => "Failed to parse tx") - .chain((val) => unsignedEVMTransactionCodec.decode(val)) - .map((v) => prepareEVMTx({ address, decodedTx: v })); - } - - if (isSolanaChain(network)) { - return unsignedSolanaTransactionCodec.decode(tx); - } - - if (isTonChain(network)) { - return Either.encase(() => JSON.parse(tx)) - .mapLeft(() => "Failed to parse tx") - .chain((val) => unsignedTonTransactionCodec.decode(val)); - } - - return Left("Unsupported network"); - }) + Either.encase(() => JSON.parse(tx)) + .chain((val) => unsignedEVMTransactionCodec.decode(val)) .mapLeft((e) => { console.log(e); return new TransactionDecodeError(); }) - ) - .chain((val) => conn.sendTransaction(val, txMeta)) - .map((val) => ({ signedTx: val, broadcasted: true })); - } - - /** - * Safe connector - */ - if (isSafeConnector(conn)) { - return EitherAsync.liftEither( - Either.encase(() => JSON.parse(tx)) - .chain((val) => unsignedEVMTransactionCodec.decode(val)) - .map((val) => prepareEVMTx({ address, decodedTx: val })) - .mapLeft(() => new TransactionDecodeError()) - ) - .chain(({ tx }) => - conn - .sendTransactions({ - txs: [ - { - data: tx.data, - to: tx.to, - value: tx.value ?? "0", - }, - ], - }) - .map((res) => res.safeTxHash) - ) - .chain((safeTxHash) => - withRequestErrorRetry({ - fn: () => - conn - .getTxStatus(safeTxHash) - .chain((res) => - !res.txHash || res.txStatus !== conn.txStatus.SUCCESS - ? EitherAsync.liftEither( - Left( - new SafeFailedError( - res.txStatus === conn.txStatus.FAILED || - res.txStatus === conn.txStatus.CANCELLED - ? "FAILED" - : "NOT_READY" - ) - ) - ) - : EitherAsync.liftEither(Right(res.txHash)) - ) - .run() - .then((res) => res.unsafeCoerce()), - shouldRetry: (error, retryCount) => - Maybe.fromNullable(error) - .chainNullable((e) => - (e as SafeFailedError)._tag === "SafeFailedError" - ? (e as SafeFailedError) - : null - ) - .filter((e) => e.type !== "FAILED" && !checkIsUnmounted()) - .map(() => retryCount < 120) - .orDefault(false), - retryWaitForMs: () => 7000, + ).chain((val) => + EitherAsync(() => + /** + * Params need to be in strict format, don't spread the object(val)! + */ + sendTransactionAsync({ + data: val.data, + to: val.to, + value: val.value, + nonce: val.nonce, + maxFeePerGas: val.maxFeePerGas, + maxPriorityFeePerGas: val.maxPriorityFeePerGas, + chainId: val.chainId, + gas: val.gasLimit, + type: val.maxFeePerGas ? "eip1559" : "legacy", }) ) - .mapLeft(() => new Error("sendTransactions failed")) - .map((val) => ({ signedTx: val as Hash, broadcasted: true })); - } - - /** - * EVM connector - */ - return EitherAsync.liftEither( - Either.encase(() => JSON.parse(tx)) - .chain((val) => unsignedEVMTransactionCodec.decode(val)) - .mapLeft((e) => { - console.log(e); - return new TransactionDecodeError(); - }) - ).chain((val) => - EitherAsync(() => - /** - * Params need to be in strict format, don't spread the object(val)! - */ - sendTransactionAsync({ - data: val.data, - to: val.to, - value: val.value, - nonce: val.nonce, - maxFeePerGas: val.maxFeePerGas, - maxPriorityFeePerGas: val.maxPriorityFeePerGas, - chainId: val.chainId, - gas: val.gasLimit, - type: val.maxFeePerGas ? "eip1559" : "legacy", - }) - ) - .mapLeft(() => new SendTransactionError()) - .map((val) => ({ signedTx: val, broadcasted: true })) - ); - }), + .mapLeft(() => new SendTransactionError()) + .map((val) => ({ signedTx: val, broadcasted: true })) + ); + }), [ connectorDetails, cosmosCW, diff --git a/packages/widget/src/providers/sk-wallet/validation.ts b/packages/widget/src/providers/sk-wallet/validation.ts index 4c094e18..d41a3f7f 100644 --- a/packages/widget/src/providers/sk-wallet/validation.ts +++ b/packages/widget/src/providers/sk-wallet/validation.ts @@ -1,10 +1,15 @@ -import type { - SolanaTx, - TonTx, -} from "@sk-widget/domain/types/wallets/generic-wallet"; -import type { Transaction as TronTx } from "@tronweb3/tronwallet-abstract-adapter"; import type { GetType } from "purify-ts"; -import { Codec, Left, Right, number, optional } from "purify-ts"; +import { + Codec, + Left, + Right, + boolean, + number, + optional, + record, + string, + unknown, +} from "purify-ts"; import type { Address, Hex } from "viem"; const bigintCodec = Codec.custom({ @@ -51,43 +56,16 @@ export const unsignedEVMTransactionCodec = Codec.interface({ export type DecodedEVMTransaction = GetType; -export const unsignedTronTransactionCodec = Codec.custom({ - decode(value) { - const val = value as Partial; - - if (val.raw_data && val.raw_data_hex && val.txID && "visible" in val) { - return Right(val as TronTx); - } - return Left("Invalid Tron transaction"); - }, - encode(value) { - return value; - }, +export const unsignedTronTransactionCodec = Codec.interface({ + raw_data: record(string, unknown), + raw_data_hex: string, + txID: string, + visible: boolean, }); -export const unsignedSolanaTransactionCodec = Codec.custom({ - decode(value) { - if (typeof value !== "string") { - return Left("Invalid solana transaction"); - } +export const unsignedSolanaTransactionCodec = string; - return Right({ type: "solana", tx: value } as SolanaTx); - }, - encode(value) { - return value; - }, -}); - -export const unsignedTonTransactionCodec = Codec.custom({ - decode(value) { - const val = value as Partial; - - if (typeof val.seqno === "number" && typeof val.message === "string") { - return Right({ type: "ton", tx: val } as TonTx); - } - return Left("Invalid TON transaction"); - }, - encode(value) { - return value; - }, +export const unsignedTonTransactionCodec = Codec.interface({ + seqno: bigintCodec, + message: string, }); diff --git a/packages/widget/src/providers/tracking/index.tsx b/packages/widget/src/providers/tracking/index.tsx index eaad91da..43fe2e97 100644 --- a/packages/widget/src/providers/tracking/index.tsx +++ b/packages/widget/src/providers/tracking/index.tsx @@ -55,6 +55,7 @@ const trackEventMap = { validatorsSubmitted: "Validators submitted", validatorImported: "Validator imported", viewTxClicked: "View transaction clicked", + actionStepsCancelled: "Action steps cancelled", initYield: "system/initYield", initToken: "system/initToken", } as const; diff --git a/packages/widget/src/translation/English/translations.json b/packages/widget/src/translation/English/translations.json index a229db4c..e8379b25 100644 --- a/packages/widget/src/translation/English/translations.json +++ b/packages/widget/src/translation/English/translations.json @@ -211,7 +211,9 @@ "LUGANODES_EXIT_REQUEST": "LUGANODES EXIT REQUEST", "INFSTONES_PROVISION": "INFSTONES PROVISION", "INFSTONES_EXIT_REQUEST": "INFSTONES EXIT REQUEST", - "INFSTONES_CLAIM_REQUEST": "INFSTONES CLAIM REQUEST" + "INFSTONES_CLAIM_REQUEST": "INFSTONES CLAIM REQUEST", + "WRAP": "WRAP", + "UNWRAP": "UNWRAP" } }, "complete": { diff --git a/packages/widget/src/translation/French/translations.json b/packages/widget/src/translation/French/translations.json index 25a95415..f01af047 100644 --- a/packages/widget/src/translation/French/translations.json +++ b/packages/widget/src/translation/French/translations.json @@ -211,7 +211,9 @@ "LUGANODES_EXIT_REQUEST": "DEMANDE DE SORTIE LUGANODES", "INFSTONES_PROVISION": "FOURNITURE INFSTONES", "INFSTONES_EXIT_REQUEST": "DEMANDE DE SORTIE INFSTONES", - "INFSTONES_CLAIM_REQUEST": "DEMANDE DE RÉCLAMATION INFSTONES" + "INFSTONES_CLAIM_REQUEST": "DEMANDE DE RÉCLAMATION INFSTONES", + "WRAP": "ENVELOPER", + "UNWRAP": "DÉENVELOPER" } }, "complete": { diff --git a/packages/widget/tests/use-cases/sk-wallet.test.tsx b/packages/widget/tests/use-cases/sk-wallet.test.tsx index 90536eac..01b42026 100644 --- a/packages/widget/tests/use-cases/sk-wallet.test.tsx +++ b/packages/widget/tests/use-cases/sk-wallet.test.tsx @@ -72,6 +72,15 @@ describe("SK Wallet", () => { actionId: "", actionType: "STAKE", txType: "APPROVAL", + amount: "100", + inputToken: { + address: "", + decimals: 0, + symbol: "", + name: "", + network: "solana", + }, + providersDetails: [], }, ledgerHwAppId: null, }); @@ -90,6 +99,15 @@ describe("SK Wallet", () => { actionId: "", actionType: "STAKE", txType: "APPROVAL", + amount: "100", + inputToken: { + address: "", + decimals: 0, + symbol: "", + name: "", + network: "solana", + }, + providersDetails: [], } ); }); @@ -128,6 +146,15 @@ describe("SK Wallet", () => { actionId: "", actionType: "STAKE", txType: "APPROVAL", + amount: "100", + inputToken: { + address: "", + decimals: 0, + symbol: "", + name: "", + network: "ton", + }, + providersDetails: [], }, ledgerHwAppId: null, }); @@ -139,13 +166,22 @@ describe("SK Wallet", () => { expect(sendTransactionSpy).toHaveBeenCalledWith( { type: "ton", - tx: { seqno: 0, message: "12345" }, + tx: { seqno: 0n, message: "12345" }, }, { txId: "", actionId: "", actionType: "STAKE", txType: "APPROVAL", + amount: "100", + inputToken: { + address: "", + decimals: 0, + symbol: "", + name: "", + network: "ton", + }, + providersDetails: [], } ); }); diff --git a/packages/widget/tests/use-cases/staking-flow/setup.ts b/packages/widget/tests/use-cases/staking-flow/setup.ts index bafad682..9a6f6431 100644 --- a/packages/widget/tests/use-cases/staking-flow/setup.ts +++ b/packages/widget/tests/use-cases/staking-flow/setup.ts @@ -132,6 +132,7 @@ export const setup = async () => { validatorAddresses: null, completedAt: null, createdAt: "2023-12-28T14:36:21.700Z", + projectId: "projectId", transactions: [ { id: "9aa80e02-3d81-4b0f-aa0b-155138b77293", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e711fd60..6d81b3e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,8 +185,8 @@ importers: specifier: ^9.1.0 version: 9.1.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2) '@stakekit/api-hooks': - specifier: 0.0.98 - version: 0.0.98(@faker-js/faker@9.7.0)(@tanstack/react-query@5.74.0(react@19.1.0))(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(react@19.1.0) + specifier: 0.0.100 + version: 0.0.100(@faker-js/faker@9.7.0)(@tanstack/react-query@5.74.0(react@19.1.0))(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(react@19.1.0) '@stakekit/common': specifier: ^0.0.48 version: 0.0.48 @@ -2538,8 +2538,8 @@ packages: '@stablelib/x25519@1.0.3': resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} - '@stakekit/api-hooks@0.0.98': - resolution: {integrity: sha512-QxHYi7JnoU/FYViUp27zk+JhUAu37ziKQXfr9BsUQb5weza46jjIEy+B29T/cc7BH8iHtA+R7I2OiCG10IEiTQ==} + '@stakekit/api-hooks@0.0.100': + resolution: {integrity: sha512-vXvZHDU6Wzkt6349D86gw2S7YqgzmvMzQdjXglkdsfVP6p6tz02BJxkZ8x+nIKwRwN9WteIHmIFUwPJiybHUsA==} peerDependencies: '@faker-js/faker': ^9 '@tanstack/react-query': '>=5' @@ -9025,7 +9025,7 @@ snapshots: '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 - '@stakekit/api-hooks@0.0.98(@faker-js/faker@9.7.0)(@tanstack/react-query@5.74.0(react@19.1.0))(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(react@19.1.0)': + '@stakekit/api-hooks@0.0.100(@faker-js/faker@9.7.0)(@tanstack/react-query@5.74.0(react@19.1.0))(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: