@ton/appkit-react 1.0.0-alpha.1 → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/esm/components/shared/amount-preview/amount-preview.js +17 -0
  2. package/dist/esm/components/shared/amount-preview/amount-preview.js.map +1 -0
  3. package/dist/esm/components/shared/amount-preview/amount-preview.module.css +40 -0
  4. package/dist/esm/components/shared/amount-preview/index.js +9 -0
  5. package/dist/esm/components/shared/amount-preview/index.js.map +1 -0
  6. package/dist/esm/components/shared/flow-preview/flow-preview.js +24 -0
  7. package/dist/esm/components/shared/flow-preview/flow-preview.js.map +1 -0
  8. package/dist/esm/components/shared/flow-preview/flow-preview.module.css +37 -0
  9. package/dist/esm/components/shared/flow-preview/index.js +9 -0
  10. package/dist/esm/components/shared/flow-preview/index.js.map +1 -0
  11. package/dist/esm/components/ui/logo/logo.module.css +1 -3
  12. package/dist/esm/features/staking/components/staking-confirm-modal/index.js +9 -0
  13. package/dist/esm/features/staking/components/staking-confirm-modal/index.js.map +1 -0
  14. package/dist/esm/features/staking/components/staking-confirm-modal/staking-confirm-modal.js +46 -0
  15. package/dist/esm/features/staking/components/staking-confirm-modal/staking-confirm-modal.js.map +1 -0
  16. package/dist/esm/features/staking/components/staking-confirm-modal/staking-confirm-modal.module.css +11 -0
  17. package/dist/esm/features/staking/components/staking-widget-provider/staking-widget-provider.js +27 -4
  18. package/dist/esm/features/staking/components/staking-widget-provider/staking-widget-provider.js.map +1 -1
  19. package/dist/esm/features/staking/components/staking-widget-provider/use-staking-validation.js +12 -3
  20. package/dist/esm/features/staking/components/staking-widget-provider/use-staking-validation.js.map +1 -1
  21. package/dist/esm/features/staking/components/staking-widget-ui/staking-widget-ui.js +16 -4
  22. package/dist/esm/features/staking/components/staking-widget-ui/staking-widget-ui.js.map +1 -1
  23. package/dist/esm/features/staking/hooks/use-build-stake-transaction.js +2 -2
  24. package/dist/esm/features/staking/hooks/use-build-stake-transaction.js.map +1 -1
  25. package/dist/esm/features/staking/utils/map-staking-error.js +6 -4
  26. package/dist/esm/features/staking/utils/map-staking-error.js.map +1 -1
  27. package/dist/esm/features/swap/components/swap-confirm-modal/index.js +9 -0
  28. package/dist/esm/features/swap/components/swap-confirm-modal/index.js.map +1 -0
  29. package/dist/esm/features/swap/components/swap-confirm-modal/swap-confirm-modal.js +12 -0
  30. package/dist/esm/features/swap/components/swap-confirm-modal/swap-confirm-modal.js.map +1 -0
  31. package/dist/esm/features/swap/components/swap-confirm-modal/swap-confirm-modal.module.css +7 -0
  32. package/dist/esm/features/swap/components/swap-widget-provider/swap-widget-provider.js +28 -6
  33. package/dist/esm/features/swap/components/swap-widget-provider/swap-widget-provider.js.map +1 -1
  34. package/dist/esm/features/swap/components/swap-widget-provider/use-swap-validation.js +12 -3
  35. package/dist/esm/features/swap/components/swap-widget-provider/use-swap-validation.js.map +1 -1
  36. package/dist/esm/features/swap/components/swap-widget-ui/swap-widget-ui.js +16 -4
  37. package/dist/esm/features/swap/components/swap-widget-ui/swap-widget-ui.js.map +1 -1
  38. package/dist/esm/features/swap/utils/map-swap-error.js +5 -3
  39. package/dist/esm/features/swap/utils/map-swap-error.js.map +1 -1
  40. package/dist/esm/locales/en.js +9 -0
  41. package/dist/esm/locales/en.js.map +1 -1
  42. package/dist/esm/styles/index.css +2 -2
  43. package/dist/types/components/shared/amount-preview/amount-preview.d.ts +24 -0
  44. package/dist/types/components/shared/amount-preview/amount-preview.d.ts.map +1 -0
  45. package/dist/types/components/shared/amount-preview/index.d.ts +9 -0
  46. package/dist/types/components/shared/amount-preview/index.d.ts.map +1 -0
  47. package/dist/types/components/shared/flow-preview/flow-preview.d.ts +18 -0
  48. package/dist/types/components/shared/flow-preview/flow-preview.d.ts.map +1 -0
  49. package/dist/types/components/shared/flow-preview/index.d.ts +9 -0
  50. package/dist/types/components/shared/flow-preview/index.d.ts.map +1 -0
  51. package/dist/types/features/staking/components/staking-confirm-modal/index.d.ts +9 -0
  52. package/dist/types/features/staking/components/staking-confirm-modal/index.d.ts.map +1 -0
  53. package/dist/types/features/staking/components/staking-confirm-modal/staking-confirm-modal.d.ts +23 -0
  54. package/dist/types/features/staking/components/staking-confirm-modal/staking-confirm-modal.d.ts.map +1 -0
  55. package/dist/types/features/staking/components/staking-widget-provider/staking-widget-provider.d.ts.map +1 -1
  56. package/dist/types/features/staking/components/staking-widget-provider/use-staking-validation.d.ts +3 -1
  57. package/dist/types/features/staking/components/staking-widget-provider/use-staking-validation.d.ts.map +1 -1
  58. package/dist/types/features/staking/components/staking-widget-ui/staking-widget-ui.d.ts.map +1 -1
  59. package/dist/types/features/staking/hooks/use-build-stake-transaction.d.ts +3 -2
  60. package/dist/types/features/staking/hooks/use-build-stake-transaction.d.ts.map +1 -1
  61. package/dist/types/features/staking/utils/map-staking-error.d.ts +5 -3
  62. package/dist/types/features/staking/utils/map-staking-error.d.ts.map +1 -1
  63. package/dist/types/features/swap/components/swap-confirm-modal/index.d.ts +9 -0
  64. package/dist/types/features/swap/components/swap-confirm-modal/index.d.ts.map +1 -0
  65. package/dist/types/features/swap/components/swap-confirm-modal/swap-confirm-modal.d.ts +26 -0
  66. package/dist/types/features/swap/components/swap-confirm-modal/swap-confirm-modal.d.ts.map +1 -0
  67. package/dist/types/features/swap/components/swap-widget-provider/swap-widget-provider.d.ts.map +1 -1
  68. package/dist/types/features/swap/components/swap-widget-provider/use-swap-validation.d.ts +4 -1
  69. package/dist/types/features/swap/components/swap-widget-provider/use-swap-validation.d.ts.map +1 -1
  70. package/dist/types/features/swap/components/swap-widget-ui/swap-widget-ui.d.ts.map +1 -1
  71. package/dist/types/features/swap/utils/map-swap-error.d.ts +4 -2
  72. package/dist/types/features/swap/utils/map-swap-error.d.ts.map +1 -1
  73. package/dist/types/libs/i18n.d.ts +9 -0
  74. package/dist/types/libs/i18n.d.ts.map +1 -1
  75. package/dist/types/locales/en.d.ts +9 -0
  76. package/dist/types/locales/en.d.ts.map +1 -1
  77. package/package.json +12 -12
  78. package/src/components/shared/amount-preview/amount-preview.tsx +74 -0
  79. package/src/components/shared/amount-preview/index.ts +9 -0
  80. package/src/components/shared/flow-preview/flow-preview.tsx +64 -0
  81. package/src/components/shared/flow-preview/index.ts +9 -0
  82. package/src/features/staking/components/staking-confirm-modal/index.ts +9 -0
  83. package/src/features/staking/components/staking-confirm-modal/staking-confirm-modal.tsx +121 -0
  84. package/src/features/staking/components/staking-widget-provider/staking-widget-provider.tsx +39 -4
  85. package/src/features/staking/components/staking-widget-provider/use-staking-validation.ts +14 -2
  86. package/src/features/staking/components/staking-widget-ui/staking-widget-ui.tsx +28 -3
  87. package/src/features/staking/hooks/use-build-stake-transaction.ts +7 -2
  88. package/src/features/staking/utils/map-staking-error.ts +6 -4
  89. package/src/features/swap/components/swap-confirm-modal/index.ts +9 -0
  90. package/src/features/swap/components/swap-confirm-modal/swap-confirm-modal.tsx +75 -0
  91. package/src/features/swap/components/swap-widget-provider/swap-widget-provider.tsx +40 -6
  92. package/src/features/swap/components/swap-widget-provider/use-swap-validation.ts +17 -2
  93. package/src/features/swap/components/swap-widget-ui/swap-widget-ui.tsx +30 -3
  94. package/src/features/swap/utils/map-swap-error.ts +5 -3
  95. package/src/locales/en.ts +9 -0
  96. package/dist/esm/tsconfig.build.tsbuildinfo +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-swap-validation.d.ts","sourceRoot":"","sources":["../../../../../../src/features/swap/components/swap-widget-provider/use-swap-validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAIvE,UAAU,wBAAwB;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAC9B,UAAU,EACV,mBAAmB,EACnB,SAAS,EACT,OAAO,EACP,WAAW,EACX,UAAU,EACV,kBAAkB,GACrB,EAAE,wBAAwB;;;EAkB1B"}
1
+ {"version":3,"file":"use-swap-validation.d.ts","sourceRoot":"","sources":["../../../../../../src/features/swap/components/swap-widget-provider/use-swap-validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAIvE,UAAU,wBAAwB;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAC9B,UAAU,EACV,mBAAmB,EACnB,SAAS,EACT,OAAO,EACP,WAAW,EACX,KAAK,EACL,UAAU,EACV,SAAS,EACT,kBAAkB,GACrB,EAAE,wBAAwB;;;EA4B1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"swap-widget-ui.d.ts","sourceRoot":"","sources":["../../../../../../src/features/swap/components/swap-widget-ui/swap-widget-ui.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAYhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;AAE5E,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,qBAAqB,CA4IlD,CAAC"}
1
+ {"version":3,"file":"swap-widget-ui.d.ts","sourceRoot":"","sources":["../../../../../../src/features/swap/components/swap-widget-ui/swap-widget-ui.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAahD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;AAE5E,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,qBAAqB,CAsKlD,CAAC"}
@@ -7,7 +7,9 @@
7
7
  */
8
8
  /**
9
9
  * Map a thrown swap error to an i18n key. Tries swap-specific codes first, falls back to the
10
- * shared {@link mapDefiError} for base DeFi codes, and finally to a generic `swap.quoteError`.
10
+ * shared {@link mapDefiError} for base DeFi codes, and finally to the caller-provided
11
+ * {@link fallback} (defaults to `swap.quoteError`, but send-time callers should pass
12
+ * `swap.sendFailed`).
11
13
  */
12
- export declare const mapSwapError: (error: unknown) => string;
14
+ export declare const mapSwapError: (error: unknown, fallback?: string) => string;
13
15
  //# sourceMappingURL=map-swap-error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"map-swap-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/swap/utils/map-swap-error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAe7C,CAAC"}
1
+ {"version":3,"file":"map-swap-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/swap/utils/map-swap-error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,EAAE,WAAU,MAA0B,KAAG,MAenF,CAAC"}
@@ -66,6 +66,10 @@ export declare const i18n: rosetta.Rosetta<{
66
66
  readonly provider: "Provider";
67
67
  readonly save: "Save";
68
68
  readonly minReceived: "Min Received";
69
+ readonly confirmTitle: "Confirm swap transaction";
70
+ readonly confirm: "Confirm";
71
+ readonly sendFailed: "Transaction failed";
72
+ readonly loading: "Loading...";
69
73
  };
70
74
  readonly lowBalance: {
71
75
  readonly title: "Not enough TON";
@@ -100,6 +104,11 @@ export declare const i18n: rosetta.Rosetta<{
100
104
  readonly provider: "Provider";
101
105
  readonly settings: "Staking settings";
102
106
  readonly save: "Save";
107
+ readonly confirmStakingTitle: "Confirm staking";
108
+ readonly confirmUnstakingTitle: "Confirm unstaking";
109
+ readonly confirm: "Confirm";
110
+ readonly sendFailed: "Transaction failed";
111
+ readonly loading: "Loading...";
103
112
  };
104
113
  }>;
105
114
  export { en };
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../../src/libs/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,MAAM,eAAe,CAAC;AAE/B,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAkB,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,CAAC;AACd,eAAO,MAAM,eAAe,OAAO,CAAC;AAIpC,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC;AAC/B,MAAM,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../../src/libs/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,MAAM,eAAe,CAAC;AAE/B,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAkB,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,CAAC;AACd,eAAO,MAAM,eAAe,OAAO,CAAC;AAIpC,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC;AAC/B,MAAM,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC"}
@@ -64,6 +64,10 @@ declare const _default: {
64
64
  readonly provider: "Provider";
65
65
  readonly save: "Save";
66
66
  readonly minReceived: "Min Received";
67
+ readonly confirmTitle: "Confirm swap transaction";
68
+ readonly confirm: "Confirm";
69
+ readonly sendFailed: "Transaction failed";
70
+ readonly loading: "Loading...";
67
71
  };
68
72
  readonly lowBalance: {
69
73
  readonly title: "Not enough TON";
@@ -98,6 +102,11 @@ declare const _default: {
98
102
  readonly provider: "Provider";
99
103
  readonly settings: "Staking settings";
100
104
  readonly save: "Save";
105
+ readonly confirmStakingTitle: "Confirm staking";
106
+ readonly confirmUnstakingTitle: "Confirm unstaking";
107
+ readonly confirm: "Confirm";
108
+ readonly sendFailed: "Transaction failed";
109
+ readonly loading: "Loading...";
101
110
  };
102
111
  };
103
112
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/locales/en.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,wBAiHW"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/locales/en.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,wBA0HW"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton/appkit-react",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,27 +33,27 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "clsx": "2.1.1",
36
+ "clsx": "^2.1.1",
37
37
  "radix-ui": "^1.4.3",
38
38
  "rosetta": "1.1.0",
39
- "@ton/appkit": "1.0.0-alpha.1"
39
+ "@ton/appkit": "1.0.0-alpha.2"
40
40
  },
41
41
  "devDependencies": {
42
- "@storybook/addon-docs": "10.3.5",
43
- "@storybook/react": "10.3.5",
44
- "@storybook/react-vite": "10.3.5",
42
+ "@storybook/addon-docs": "10.4.1",
43
+ "@storybook/react": "10.4.1",
44
+ "@storybook/react-vite": "10.4.1",
45
45
  "@storybook/test": "^8.6.15",
46
- "@tanstack/react-query": "5.99.0",
47
- "@tonconnect/ui-react": "2.5.0-alpha.0",
46
+ "@tanstack/react-query": "^5.100.14",
47
+ "@tonconnect/ui-react": "^2.5.0-alpha.0",
48
48
  "@types/react": "19.2.3",
49
49
  "@types/react-dom": "19.2.3",
50
+ "buffer": "^6.0.3",
50
51
  "copyfiles": "2.4.1",
51
52
  "react": "19.2.3",
52
53
  "react-dom": "19.2.3",
53
- "storybook": "10.3.5",
54
- "typescript": "^5.9.3",
55
- "vite": "^8.0.8",
56
- "vite-plugin-node-polyfills": "^0.26.0"
54
+ "storybook": "10.4.1",
55
+ "typescript": "5.9.3",
56
+ "vite": "8.0.14"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@tanstack/react-query": ">=5.0.0",
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Copyright (c) TonTech.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type { FC, ComponentProps } from 'react';
10
+ import { calcFiatValue, formatLargeValue } from '@ton/appkit';
11
+ import clsx from 'clsx';
12
+
13
+ import { Logo } from '../../ui/logo';
14
+ import { TonIconCircle } from '../../ui/icons';
15
+ import type { AppkitUIToken } from '../../../types/appkit-ui-token';
16
+ import { getDisplayAmount } from '../../../features/swap/utils/get-display-amount';
17
+ import styles from './amount-preview.module.css';
18
+
19
+ export interface AmountPreviewProps extends ComponentProps<'div'> {
20
+ /** Raw token amount to display (decimal string). */
21
+ amount: string;
22
+ /** Token whose logo and symbol are shown alongside the amount. */
23
+ token?: AppkitUIToken;
24
+ /** Fiat currency symbol, e.g. "$". */
25
+ fiatSymbol?: string;
26
+ /**
27
+ * Relative fiat delta to render after the fiat value, e.g. -0.0025 → "(-0.25%)".
28
+ * Typically computed by a parent that knows both legs of a flow.
29
+ */
30
+ fiatDelta?: number;
31
+ }
32
+
33
+ const formatFiatDelta = (delta: number): string => {
34
+ const sign = delta > 0 ? '+' : '';
35
+ return `(${sign}${(delta * 100).toFixed(2)}%)`;
36
+ };
37
+
38
+ export const AmountPreview: FC<AmountPreviewProps> = ({
39
+ amount,
40
+ token,
41
+ fiatSymbol = '$',
42
+ fiatDelta,
43
+ className,
44
+ ...props
45
+ }) => {
46
+ const displayAmount = getDisplayAmount(amount, token?.decimals);
47
+ const fiatValue = token?.rate ? formatLargeValue(calcFiatValue(amount || '0', token.rate), 2, 2) : null;
48
+
49
+ return (
50
+ <div className={clsx(styles.container, className)} {...props}>
51
+ <div className={styles.amountRow}>
52
+ <span className={styles.amount}>{displayAmount}</span>
53
+ {token && (
54
+ <span className={styles.tokenTag}>
55
+ {token.address === 'ton' ? (
56
+ <TonIconCircle size={24} />
57
+ ) : (
58
+ <Logo size={24} src={token.logo} fallback={token.symbol?.[0] ?? '?'} alt={token.symbol} />
59
+ )}
60
+ <span className={styles.symbol}>{token.symbol}</span>
61
+ </span>
62
+ )}
63
+ </div>
64
+ {fiatValue !== null && (
65
+ <div className={styles.fiat}>
66
+ <span>
67
+ {fiatSymbol} {fiatValue}
68
+ </span>
69
+ {fiatDelta !== undefined && <span className={styles.fiatDelta}>{formatFiatDelta(fiatDelta)}</span>}
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) TonTech.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ export * from './amount-preview';
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Copyright (c) TonTech.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type { FC, ComponentProps } from 'react';
10
+ import { calcFiatValue } from '@ton/appkit';
11
+ import clsx from 'clsx';
12
+
13
+ import { ChevronDownIcon } from '../../ui/icons';
14
+ import { AmountPreview } from '../amount-preview';
15
+ import type { AppkitUIToken } from '../../../types/appkit-ui-token';
16
+ import styles from './flow-preview.module.css';
17
+
18
+ export interface FlowPreviewProps extends ComponentProps<'div'> {
19
+ fromAmount: string;
20
+ toAmount: string;
21
+ fromToken?: AppkitUIToken;
22
+ toToken?: AppkitUIToken;
23
+ fiatSymbol?: string;
24
+ }
25
+
26
+ /**
27
+ * Returns the relative fiat delta between paying `fromFiat` and receiving `toFiat`,
28
+ * or `undefined` when either side lacks fiat data.
29
+ */
30
+ const calcFiatDelta = (fromFiat: string, toFiat: string): number | undefined => {
31
+ const from = parseFloat(fromFiat);
32
+ const to = parseFloat(toFiat);
33
+ if (!from || !to) return undefined;
34
+ return (to - from) / from;
35
+ };
36
+
37
+ export const FlowPreview: FC<FlowPreviewProps> = ({
38
+ fromAmount,
39
+ toAmount,
40
+ fromToken,
41
+ toToken,
42
+ fiatSymbol = '$',
43
+ className,
44
+ ...props
45
+ }) => {
46
+ const fromFiat = calcFiatValue(fromAmount || '0', fromToken?.rate);
47
+ const toFiat = calcFiatValue(toAmount || '0', toToken?.rate);
48
+ const fiatDelta = calcFiatDelta(fromFiat, toFiat);
49
+
50
+ return (
51
+ <div className={clsx(styles.container, className)} {...props}>
52
+ <AmountPreview amount={fromAmount} token={fromToken} fiatSymbol={fiatSymbol} />
53
+
54
+ <div className={styles.separator}>
55
+ <span className={styles.separatorLine} />
56
+ <span className={styles.arrowBadge}>
57
+ <ChevronDownIcon size={16} />
58
+ </span>
59
+ </div>
60
+
61
+ <AmountPreview amount={toAmount} token={toToken} fiatSymbol={fiatSymbol} fiatDelta={fiatDelta} />
62
+ </div>
63
+ );
64
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) TonTech.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ export * from './flow-preview';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) TonTech.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ export * from './staking-confirm-modal';
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Copyright (c) TonTech.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type { FC } from 'react';
10
+ import type {
11
+ JettonInfo,
12
+ Network,
13
+ StakingProviderInfo,
14
+ StakingProviderMetadata,
15
+ StakingQuote,
16
+ StakingQuoteDirection,
17
+ StakingTokenInfo,
18
+ } from '@ton/appkit';
19
+
20
+ import { Modal } from '../../../../components/ui/modal/modal';
21
+ import { Button } from '../../../../components/ui/button';
22
+ import { AmountPreview } from '../../../../components/shared/amount-preview';
23
+ import { FlowPreview } from '../../../../components/shared/flow-preview';
24
+ import type { AppkitUIToken } from '../../../../types/appkit-ui-token';
25
+ import { useJettonInfo } from '../../../jettons';
26
+ import { useI18n } from '../../../settings/hooks/use-i18n';
27
+ import { StakingInfo } from '../staking-info';
28
+ import styles from './staking-confirm-modal.module.css';
29
+
30
+ export interface StakingConfirmModalProps {
31
+ open: boolean;
32
+ onClose: () => void;
33
+ onConfirm: () => void;
34
+ direction: StakingQuoteDirection;
35
+ network: Network | undefined;
36
+ quote: StakingQuote | undefined;
37
+ providerInfo: StakingProviderInfo | undefined;
38
+ providerMetadata: StakingProviderMetadata | undefined;
39
+ isProviderInfoLoading: boolean;
40
+ isQuoteLoading: boolean;
41
+ }
42
+
43
+ /**
44
+ * Adapter from staking-domain token shape (`StakingTokenInfo`) to the shared
45
+ * `AppkitUIToken` shape consumed by AmountPreview/FlowPreview. `name` is taken
46
+ * from the resolved jetton metadata when available, falling back to ticker.
47
+ */
48
+ const toUIToken = (
49
+ token: StakingTokenInfo | undefined,
50
+ jettonInfo: JettonInfo | null | undefined,
51
+ network: Network | undefined,
52
+ ): AppkitUIToken | undefined => {
53
+ if (!token || !network) return undefined;
54
+ return {
55
+ symbol: token.ticker,
56
+ name: jettonInfo?.name ?? token.ticker,
57
+ decimals: token.decimals,
58
+ address: token.address,
59
+ logo: token.address === 'ton' ? undefined : jettonInfo?.image,
60
+ network,
61
+ };
62
+ };
63
+
64
+ export const StakingConfirmModal: FC<StakingConfirmModalProps> = ({
65
+ open,
66
+ onClose,
67
+ onConfirm,
68
+ direction,
69
+ network,
70
+ quote,
71
+ providerInfo,
72
+ providerMetadata,
73
+ isProviderInfoLoading,
74
+ isQuoteLoading,
75
+ }) => {
76
+ const { t } = useI18n();
77
+
78
+ const stakeAddress = providerMetadata?.stakeToken.address;
79
+ const receiveAddress = providerMetadata?.receiveToken?.address;
80
+
81
+ const { data: stakeJettonInfo } = useJettonInfo({
82
+ address: stakeAddress,
83
+ query: { enabled: !!stakeAddress && stakeAddress !== 'ton' },
84
+ });
85
+ const { data: receiveJettonInfo } = useJettonInfo({
86
+ address: receiveAddress,
87
+ query: { enabled: !!receiveAddress && receiveAddress !== 'ton' },
88
+ });
89
+
90
+ const stakeToken = toUIToken(providerMetadata?.stakeToken, stakeJettonInfo, network);
91
+ const receiveToken = toUIToken(providerMetadata?.receiveToken, receiveJettonInfo, network);
92
+
93
+ const title = direction === 'stake' ? t('staking.confirmStakingTitle') : t('staking.confirmUnstakingTitle');
94
+
95
+ const amountIn = quote?.amountIn ?? '0';
96
+ const amountOut = quote?.amountOut ?? '0';
97
+
98
+ return (
99
+ <Modal open={open} onOpenChange={(isOpen) => !isOpen && onClose()} title={title}>
100
+ {direction === 'stake' ? (
101
+ <AmountPreview className={styles.singleAmount} amount={amountIn} token={stakeToken} />
102
+ ) : (
103
+ <FlowPreview fromAmount={amountIn} toAmount={amountOut} fromToken={receiveToken} toToken={stakeToken} />
104
+ )}
105
+
106
+ <StakingInfo
107
+ className={styles.info}
108
+ quote={quote}
109
+ isQuoteLoading={isQuoteLoading}
110
+ providerInfo={providerInfo}
111
+ providerMetadata={providerMetadata}
112
+ isProviderInfoLoading={isProviderInfoLoading}
113
+ direction={direction}
114
+ />
115
+
116
+ <Button className={styles.confirmButton} variant="fill" size="l" fullWidth onClick={onConfirm}>
117
+ {t('staking.confirm')}
118
+ </Button>
119
+ </Modal>
120
+ );
121
+ };
@@ -6,7 +6,7 @@
6
6
  *
7
7
  */
8
8
 
9
- import { createContext, useCallback, useContext, useMemo, useState } from 'react';
9
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
10
10
  import type { FC, PropsWithChildren } from 'react';
11
11
  import type { Network, StakingProvider, StakingQuoteDirection, TonShortfall } from '@ton/appkit';
12
12
  import {
@@ -224,8 +224,39 @@ export const StakingWidgetProvider: FC<StakingProviderProps> = ({ children, netw
224
224
  query: { refetchInterval: 5000 },
225
225
  });
226
226
 
227
- const { mutateAsync: buildTransaction } = useBuildStakeTransaction();
228
- const { mutateAsync: sendTransaction, isPending: isSendingTransaction } = useSendTransaction();
227
+ const {
228
+ mutateAsync: buildTransaction,
229
+ isPending: isBuildingTransaction,
230
+ error: buildError,
231
+ reset: resetBuild,
232
+ } = useBuildStakeTransaction({ mutation: { networkMode: 'always' } });
233
+ const {
234
+ mutateAsync: sendTransaction,
235
+ isPending: isSendingPending,
236
+ error: sendMutationError,
237
+ reset: resetSend,
238
+ } = useSendTransaction({ mutation: { networkMode: 'always' } });
239
+ const isSendingTransaction = isBuildingTransaction || isSendingPending;
240
+ const sendError = sendMutationError ?? buildError;
241
+
242
+ const resetSendError = useCallback(() => {
243
+ resetBuild();
244
+ resetSend();
245
+ }, [resetBuild, resetSend]);
246
+
247
+ // Drop the previous send error when the user changes anything that invalidates it —
248
+ // the next attempt is conceptually a new stake, no need to keep the old message on screen.
249
+ useEffect(() => {
250
+ resetSendError();
251
+ }, [direction, amount, isReversed, resetSendError]);
252
+
253
+ // Auto-clear the send error after a short delay so a stale failure doesn't linger in the
254
+ // submit button — the user is expected to act on it within seconds or move on.
255
+ useEffect(() => {
256
+ if (!sendError) return;
257
+ const id = setTimeout(resetSendError, 5000);
258
+ return () => clearTimeout(id);
259
+ }, [sendError, resetSendError]);
229
260
 
230
261
  const amountDecimals = useMemo(() => {
231
262
  const unstakeDecimals = isReversed
@@ -256,7 +287,10 @@ export const StakingWidgetProvider: FC<StakingProviderProps> = ({ children, netw
256
287
  data: quote,
257
288
  isFetching: isQuoteLoading,
258
289
  error: quoteError,
259
- } = useStakingQuote({ ...quoteParamsDebounced, query: { enabled: isNetworkSupported } });
290
+ } = useStakingQuote({
291
+ ...quoteParamsDebounced,
292
+ query: { enabled: isNetworkSupported, networkMode: 'always', retry: false, gcTime: 0 },
293
+ });
260
294
 
261
295
  const reversedAmount = useMemo(() => {
262
296
  if (direction === 'unstake' && isReversed) return quote?.amountIn || '0';
@@ -337,6 +371,7 @@ export const StakingWidgetProvider: FC<StakingProviderProps> = ({ children, netw
337
371
  amountDebounced: quoteParamsDebounced.amount || '',
338
372
  balance,
339
373
  quoteError,
374
+ sendError,
340
375
  direction,
341
376
  stakedBalance: stakedBalanceData?.stakedBalance,
342
377
  quote,
@@ -18,6 +18,8 @@ interface UseStakingValidationOptions {
18
18
  balance: string | undefined;
19
19
  quote?: StakingQuote;
20
20
  quoteError: Error | null;
21
+ /** Error from the build/send mutation. Takes priority over input validation but does not block submit. */
22
+ sendError: Error | null;
21
23
  direction: StakingQuoteDirection;
22
24
  amountDecimals?: number;
23
25
  isReversed: boolean;
@@ -31,13 +33,16 @@ export const useStakingValidation = ({
31
33
  balance,
32
34
  quote,
33
35
  quoteError,
36
+ sendError,
34
37
  direction,
35
38
  amountDecimals,
36
39
  isReversed,
37
40
  stakedBalance,
38
41
  isNetworkSupported,
39
42
  }: UseStakingValidationOptions) => {
40
- const error: string | null = useMemo(() => {
43
+ // Input-side validation that blocks submission. `sendError` is intentionally NOT considered
44
+ // here — a previous failed attempt shouldn't lock the button against a retry.
45
+ const blockingError: string | null = useMemo(() => {
41
46
  if (!isNetworkSupported) return 'defi.unsupportedNetwork';
42
47
 
43
48
  if ((parseFloat(amount) || 0) <= 0) return null;
@@ -73,7 +78,14 @@ export const useStakingValidation = ({
73
78
  amountDecimals,
74
79
  ]);
75
80
 
76
- const canSubmit = (parseFloat(amount) || 0) > 0 && error === null;
81
+ // The user-visible error: build/send failure (most recent user action) wins over background
82
+ // validation noise; falls back to validation when no send error is active.
83
+ const error = useMemo<string | null>(() => {
84
+ if (sendError) return mapStakingError(sendError, 'staking.sendFailed');
85
+ return blockingError;
86
+ }, [sendError, blockingError]);
87
+
88
+ const canSubmit = (parseFloat(amount) || 0) > 0 && blockingError === null && quote !== undefined;
77
89
 
78
90
  return { error, canSubmit };
79
91
  };
@@ -6,7 +6,7 @@
6
6
  *
7
7
  */
8
8
 
9
- import { useMemo, useState } from 'react';
9
+ import { useCallback, useMemo, useState } from 'react';
10
10
  import type { ComponentProps, FC, ReactNode } from 'react';
11
11
  import type { StakingQuoteDirection } from '@ton/appkit';
12
12
  import clsx from 'clsx';
@@ -15,6 +15,7 @@ import { CenteredAmountInput } from '../../../../components/ui/centered-amount-i
15
15
  import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../components/ui/tabs';
16
16
  import { useI18n } from '../../../settings/hooks/use-i18n';
17
17
  import { StakingBalanceBlock } from '../staking-balance-block';
18
+ import { StakingConfirmModal } from '../staking-confirm-modal';
18
19
  import { StakingInfo } from '../staking-info';
19
20
  import { SelectUnstakeMode } from '../select-unstake-mode';
20
21
  import { StakingSettingsModal } from '../staking-settings-modal';
@@ -66,14 +67,25 @@ export const StakingWidgetUI: FC<StakingWidgetRenderProps> = ({
66
67
  const { t } = useI18n();
67
68
 
68
69
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
70
+ const [isConfirmOpen, setIsConfirmOpen] = useState(false);
69
71
 
70
72
  const receiveToken = providerMetadata?.receiveToken;
71
73
  const stakeToken = providerMetadata?.stakeToken;
72
74
 
73
75
  const buttonText = useMemo(() => {
76
+ if (isSendingTransaction || isQuoteLoading) return t('staking.loading');
74
77
  if (error) return t(error);
75
78
  return direction === 'stake' ? t('staking.continue') : t('staking.unstake');
76
- }, [error, direction, t]);
79
+ }, [isSendingTransaction, isQuoteLoading, error, direction, t]);
80
+
81
+ // Close the modal immediately; the build/send result (including errors) is surfaced
82
+ // back in the widget's main button via the `error` from the provider.
83
+ const handleConfirm = useCallback(() => {
84
+ setIsConfirmOpen(false);
85
+ sendTransaction().catch(() => {
86
+ // Error is captured by the mutation and shown through the validator's `error` output.
87
+ });
88
+ }, [sendTransaction]);
77
89
 
78
90
  const submitActions: ReactNode = (
79
91
  <div className={styles.actions}>
@@ -82,7 +94,7 @@ export const StakingWidgetUI: FC<StakingWidgetRenderProps> = ({
82
94
  size="l"
83
95
  fullWidth
84
96
  disabled={!canSubmit || isQuoteLoading || isSendingTransaction}
85
- onClick={sendTransaction}
97
+ onClick={() => setIsConfirmOpen(true)}
86
98
  >
87
99
  {buttonText}
88
100
  </ButtonWithConnect>
@@ -195,6 +207,19 @@ export const StakingWidgetUI: FC<StakingWidgetRenderProps> = ({
195
207
  onProviderChange={setStakingProviderId}
196
208
  network={network}
197
209
  />
210
+
211
+ <StakingConfirmModal
212
+ open={isConfirmOpen}
213
+ onClose={() => setIsConfirmOpen(false)}
214
+ onConfirm={handleConfirm}
215
+ direction={direction}
216
+ network={network}
217
+ quote={quote}
218
+ providerInfo={providerInfo}
219
+ providerMetadata={providerMetadata}
220
+ isProviderInfoLoading={isProviderInfoLoading}
221
+ isQuoteLoading={isQuoteLoading}
222
+ />
198
223
  </div>
199
224
  );
200
225
  };
@@ -11,12 +11,15 @@ import { buildStakeTransactionMutationOptions } from '@ton/appkit/queries';
11
11
  import type {
12
12
  BuildStakeTransactionData,
13
13
  BuildStakeTransactionErrorType,
14
+ BuildStakeTransactionMutationOptions,
14
15
  BuildStakeTransactionVariables,
15
16
  } from '@ton/appkit/queries';
16
17
 
17
18
  import { useAppKit } from '../../settings';
18
19
  import { useMutation } from '../../../libs/query';
19
20
 
21
+ export type UseBuildStakeTransactionParameters<context = unknown> = BuildStakeTransactionMutationOptions<context>;
22
+
20
23
  export type UseBuildStakeTransactionReturnType<context = unknown> = UseMutationResult<
21
24
  BuildStakeTransactionData,
22
25
  BuildStakeTransactionErrorType,
@@ -27,7 +30,9 @@ export type UseBuildStakeTransactionReturnType<context = unknown> = UseMutationR
27
30
  /**
28
31
  * Hook to build stake transaction
29
32
  */
30
- export const useBuildStakeTransaction = <context = unknown>(): UseBuildStakeTransactionReturnType<context> => {
33
+ export const useBuildStakeTransaction = <context = unknown>(
34
+ parameters?: UseBuildStakeTransactionParameters<context>,
35
+ ): UseBuildStakeTransactionReturnType<context> => {
31
36
  const appKit = useAppKit();
32
- return useMutation(buildStakeTransactionMutationOptions<context>(appKit));
37
+ return useMutation(buildStakeTransactionMutationOptions<context>(appKit, parameters));
33
38
  };