@rozoai/intent-pay 0.1.37 → 0.1.38-beta.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 (66) hide show
  1. package/build/components/Common/TokenChainLogo/index.d.ts +7 -1
  2. package/build/components/Spinners/TokenLogoSpinner/index.d.ts +6 -1
  3. package/build/hooks/useDepositAddressOptions.d.ts +3 -2
  4. package/build/hooks/usePaymentState.d.ts +11 -5
  5. package/build/hooks/useRozoPay.d.ts +4 -4
  6. package/build/hooks/useSolanaPaymentOptions.d.ts +1 -1
  7. package/build/hooks/useStellarPaymentOptions.d.ts +1 -1
  8. package/build/hooks/useWalletPaymentOptions.d.ts +12 -7
  9. package/build/package.json.js +2 -2
  10. package/build/payment/createPaymentPayload.d.ts +3 -3
  11. package/build/payment/paymentFsm.d.ts +25 -2
  12. package/build/provider/PayContext.d.ts +3 -5
  13. package/build/src/components/Common/AmountInput/index.js +1 -1
  14. package/build/src/components/Common/AmountInput/index.js.map +1 -1
  15. package/build/src/components/Common/ConnectorList/index.js +2 -4
  16. package/build/src/components/Common/ConnectorList/index.js.map +1 -1
  17. package/build/src/components/Common/TokenChainLogo/index.js +26 -3
  18. package/build/src/components/Common/TokenChainLogo/index.js.map +1 -1
  19. package/build/src/components/Pages/MobileConnectors/index.js +4 -10
  20. package/build/src/components/Pages/MobileConnectors/index.js.map +1 -1
  21. package/build/src/components/Pages/PayWithToken/index.js +15 -4
  22. package/build/src/components/Pages/PayWithToken/index.js.map +1 -1
  23. package/build/src/components/Pages/SelectDepositAddressChain/index.js +6 -17
  24. package/build/src/components/Pages/SelectDepositAddressChain/index.js.map +1 -1
  25. package/build/src/components/Pages/SelectMethod/index.js +2 -6
  26. package/build/src/components/Pages/SelectMethod/index.js.map +1 -1
  27. package/build/src/components/Pages/Solana/PayWithSolanaToken/index.js +64 -26
  28. package/build/src/components/Pages/Solana/PayWithSolanaToken/index.js.map +1 -1
  29. package/build/src/components/Pages/Stellar/PayWithStellarToken/index.js +40 -38
  30. package/build/src/components/Pages/Stellar/PayWithStellarToken/index.js.map +1 -1
  31. package/build/src/components/Pages/WaitingDepositAddress/index.js +110 -33
  32. package/build/src/components/Pages/WaitingDepositAddress/index.js.map +1 -1
  33. package/build/src/components/RozoPayModal/index.js +9 -32
  34. package/build/src/components/RozoPayModal/index.js.map +1 -1
  35. package/build/src/components/Spinners/TokenLogoSpinner/index.js +5 -2
  36. package/build/src/components/Spinners/TokenLogoSpinner/index.js.map +1 -1
  37. package/build/src/hooks/useDepositAddressOptions.js +19 -9
  38. package/build/src/hooks/useDepositAddressOptions.js.map +1 -1
  39. package/build/src/hooks/usePaymentState.js +193 -72
  40. package/build/src/hooks/usePaymentState.js.map +1 -1
  41. package/build/src/hooks/useRozoPay.js +4 -2
  42. package/build/src/hooks/useRozoPay.js.map +1 -1
  43. package/build/src/hooks/useSolanaPaymentOptions.js +45 -12
  44. package/build/src/hooks/useSolanaPaymentOptions.js.map +1 -1
  45. package/build/src/hooks/useStellarPaymentOptions.js +40 -10
  46. package/build/src/hooks/useStellarPaymentOptions.js.map +1 -1
  47. package/build/src/hooks/useTokenOptions.js +4 -3
  48. package/build/src/hooks/useTokenOptions.js.map +1 -1
  49. package/build/src/hooks/useWalletPaymentOptions.js +54 -21
  50. package/build/src/hooks/useWalletPaymentOptions.js.map +1 -1
  51. package/build/src/payment/createPaymentPayload.js +38 -13
  52. package/build/src/payment/createPaymentPayload.js.map +1 -1
  53. package/build/src/payment/paymentEffects.js +4 -5
  54. package/build/src/payment/paymentEffects.js.map +1 -1
  55. package/build/src/payment/paymentFsm.js.map +1 -1
  56. package/build/src/provider/RozoPayProvider.js +0 -6
  57. package/build/src/provider/RozoPayProvider.js.map +1 -1
  58. package/build/src/utils/format.js +82 -13
  59. package/build/src/utils/format.js.map +1 -1
  60. package/build/src/utils/token.js +21 -5
  61. package/build/src/utils/token.js.map +1 -1
  62. package/build/src/wallets/walletConfigs.js +1 -1
  63. package/build/src/wallets/walletConfigs.js.map +1 -1
  64. package/build/utils/format.d.ts +44 -3
  65. package/build/utils/token.d.ts +6 -2
  66. package/package.json +2 -2
@@ -1,12 +1,15 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { AnimatePresence } from 'framer-motion';
3
3
  import { chainToLogo } from '../../../assets/chains.js';
4
+ import { isNativeToken } from '@rozoai/intent-common';
4
5
  import CircleSpinner from '../CircleSpinner/index.js';
5
6
  import { LoadingContainer, AnimationContainer } from '../styles.js';
6
7
  import { ChainLogoContainer } from './styles.js';
7
8
 
8
- const TokenLogoSpinner = ({ token, loading = false }) => {
9
- return (jsx(LoadingContainer, { children: jsx(AnimationContainer, { "$circle": true, children: jsxs(AnimatePresence, { children: [chainToLogo[token.chainId] && (jsx(ChainLogoContainer, { children: chainToLogo[token.chainId] }, "ChainLogoContainer")), jsx(CircleSpinner, { logo: jsx("img", { src: token.logoURI, alt: token.symbol }), loading: loading, unavailable: false }, "CircleSpinner")] }) }) }));
9
+ const TokenLogoSpinner = ({ token, loading = false, nativeAsChainIcon = false, }) => {
10
+ const chainLogo = chainToLogo[token.chainId];
11
+ const showChainAsMain = nativeAsChainIcon && isNativeToken(token.token) && chainLogo != null;
12
+ return (jsx(LoadingContainer, { children: jsx(AnimationContainer, { "$circle": true, children: jsxs(AnimatePresence, { children: [chainLogo && !showChainAsMain && (jsx(ChainLogoContainer, { children: chainLogo }, "ChainLogoContainer")), jsx(CircleSpinner, { logo: showChainAsMain ? (chainLogo) : (jsx("img", { src: token.logoURI, alt: token.symbol })), loading: loading, unavailable: false }, "CircleSpinner")] }) }) }));
10
13
  };
11
14
 
12
15
  export { TokenLogoSpinner as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../src/components/Spinners/TokenLogoSpinner/index.tsx"],"sourcesContent":[null],"names":["_jsx","_jsxs"],"mappings":";;;;;;;AAOM,MAAA,gBAAgB,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,EAA8C,KAAI;AAClG,IAAA,QACEA,GAAA,CAAC,gBAAgB,EAAA,EAAA,QAAA,EACfA,GAAC,CAAA,kBAAkB,EAAU,EAAA,SAAA,EAAA,IAAI,EAC/B,QAAA,EAAAC,IAAA,CAAC,eAAe,EAAA,EAAA,QAAA,EAAA,CACb,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,KACzBD,GAAA,CAAC,kBAAkB,EAChB,EAAA,QAAA,EAAA,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,EAAA,EADL,oBAAoB,CAEvB,CACtB,EACDA,GAAA,CAAC,aAAa,EAAA,EAEZ,IAAI,EAAEA,aAAK,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,EAAI,CAAA,EACpD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,EAHd,EAAA,eAAe,CAInB,CAAA,EAAA,CACc,EACC,CAAA,EAAA,CACJ,EACnB;AACJ;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../../src/components/Spinners/TokenLogoSpinner/index.tsx"],"sourcesContent":[null],"names":["_jsx","_jsxs"],"mappings":";;;;;;;;AAQA,MAAM,gBAAgB,GAAG,CAAC,EACxB,KAAK,EACL,OAAO,GAAG,KAAK,EACf,iBAAiB,GAAG,KAAK,GAS1B,KAAI;IACH,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAC7C,IAAA,MAAM,eAAe,GAAG,iBAAiB,IAAI,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,SAAS,IAAI,IAAI,CAAC;AAE7F,IAAA,QACEA,GAAA,CAAC,gBAAgB,EAAA,EAAA,QAAA,EACfA,IAAC,kBAAkB,EAAA,EAAA,SAAA,EAAU,IAAI,EAAA,QAAA,EAC/BC,KAAC,eAAe,EAAA,EAAA,QAAA,EAAA,CAEb,SAAS,IAAI,CAAC,eAAe,KAC5BD,GAAA,CAAC,kBAAkB,EAAA,EAAA,QAAA,EAChB,SAAS,EAAA,EADY,oBAAoB,CAEvB,CACtB,EACDA,GAAA,CAAC,aAAa,EAEZ,EAAA,IAAI,EACF,eAAe,IACb,SAAS,KAETA,GAAK,CAAA,KAAA,EAAA,EAAA,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,EAAI,CAAA,CAC/C,EAEH,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,EAAA,EATd,eAAe,CAUnB,CAAA,EAAA,CACc,EACC,CAAA,EAAA,CACJ,EACnB;AACJ;;;;"}
@@ -62,14 +62,15 @@ const fallbackOptions = [
62
62
  * - Ethereum (Chain ID: 1) - Ethereum mainnet
63
63
  * - BSC (Chain ID: 56) - Conditional support for MP app IDs
64
64
  */
65
- function useDepositAddressOptions({ trpc, usdRequired, payParams, }) {
65
+ function useDepositAddressOptions({ trpc, usdRequired, mode, payParams, }) {
66
66
  const { data, isLoading, error } = useQuery({
67
- enabled: usdRequired != null && usdRequired > 0,
68
- queryKey: ["depositAddressOptions", usdRequired],
67
+ enabled: usdRequired != null && usdRequired > 0 && mode != null,
68
+ queryKey: ["depositAddressOptions", usdRequired, mode],
69
69
  queryFn: async () => {
70
70
  try {
71
71
  return await trpc.getDepositAddressOptions.query({
72
- usdRequired: usdRequired,
72
+ usdRequired,
73
+ mode,
73
74
  });
74
75
  }
75
76
  catch (err) {
@@ -85,17 +86,26 @@ function useDepositAddressOptions({ trpc, usdRequired, payParams, }) {
85
86
  // Memoized configuration for deposit address options
86
87
  const filteredOptions = useMemo(() => {
87
88
  const options = data ?? [];
88
- if (payParams?.preferredTokens && payParams.preferredTokens.length > 0) {
89
- return options.filter((option) => payParams.preferredTokens?.some((pt) => pt.token != null &&
90
- normalizeTokenAddress(option.token.chainId, pt.token) ===
91
- normalizeTokenAddress(option.token.chainId, option.token.token)));
89
+ if (payParams?.preferredTokens && payParams?.preferredTokens.length > 0) {
90
+ // Address match: token addresses can differ in casing between the SDK
91
+ // token registry (EVM natives stored lowercase) and the API response
92
+ // (EIP-55 checksummed), so a strict `===` drops native ETH/POL/BNB
93
+ // deposit options. Normalize per-chain: EVM lowercase, Solana/Stellar
94
+ // case-sensitive.
95
+ const normalize = (chainId, addr) => normalizeTokenAddress(chainId, addr) ?? addr;
96
+ const preferred = new Set(payParams.preferredTokens.map((pt) => `${pt.chainId}:${normalize(pt.chainId, pt.token)}`));
97
+ return options.filter((option) => preferred.has(`${option.token.chainId}:${normalize(option.token.chainId, option.token.token)}`));
92
98
  }
93
99
  return options;
94
100
  }, [data, payParams?.preferredTokens]);
95
101
  return {
96
102
  options: filteredOptions,
97
103
  loading: isLoading,
98
- error: error ? String(error) : null,
104
+ error: error
105
+ ? error instanceof Error
106
+ ? error.message
107
+ : "Failed to load deposit address options"
108
+ : null,
99
109
  };
100
110
  }
101
111
 
@@ -1 +1 @@
1
- {"version":3,"file":"useDepositAddressOptions.js","sources":["../../../src/hooks/useDepositAddressOptions.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;AA8BA,MAAM,eAAe,GAAG;;AAEtB,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,aAAa;QAC9C,OAAO,EAAE,YAAY,CAAC,OAAO;AAC7B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,KAAK,EAAE,YAAY;AACpB,KAAA;AACD,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,aAAa;QAC9C,OAAO,EAAE,YAAY,CAAC,OAAO;AAC7B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,KAAK,EAAE,YAAY;AACpB,KAAA;;AAED,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,SAAS;QAC1C,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,IAAI,CAAC,OAAO;AACrB,QAAA,KAAK,EAAE,QAAQ;AAChB,KAAA;AACD,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,SAAS;QAC1C,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,IAAI,CAAC,OAAO;AACrB,QAAA,KAAK,EAAE,QAAQ;AAChB,KAAA;;AAED,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,WAAW;QAC5C,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,KAAK,EAAE,cAAc;AACtB,KAAA;AACD,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,WAAW;QAC5C,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,KAAK,EAAE,cAAc;AACtB,KAAA;CACF,CAAC;AAEF;;;;;;;;;;;;AAYG;AACG,SAAU,wBAAwB,CAAC,EACvC,IAAI,EACJ,WAAW,EACX,SAAS,GACsB,EAAA;IAC/B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAwC;AACjF,QAAA,OAAO,EAAE,WAAW,IAAI,IAAI,IAAI,WAAW,GAAG,CAAC;AAC/C,QAAA,QAAQ,EAAE,CAAC,uBAAuB,EAAE,WAAW,CAAC;QAChD,OAAO,EAAE,YAAW;AAClB,YAAA,IAAI;AACF,gBAAA,OAAO,MAAM,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC;AAC/C,oBAAA,WAAW,EAAE,WAAY;AAC1B,iBAAA,CAAC,CAAC;aACJ;YAAC,OAAO,GAAG,EAAE;;AAEZ,gBAAA,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAC;AAC7D,gBAAA,OAAO,eAAe,CAAC;aACxB;SACF;AACD,QAAA,SAAS,EAAE,MAAM;AACjB,QAAA,KAAK,EAAE,CAAC;AACR,QAAA,oBAAoB,EAAE,KAAK;AAC5B,KAAA,CAAC,CAAC;;AAGH,IAAA,MAAM,eAAe,GAAG,OAAO,CAAC,MAAK;AACnC,QAAA,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;AAC3B,QAAA,IAAI,SAAS,EAAE,eAAe,IAAI,SAAS,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE;YACtE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,KAC3B,SAAS,CAAC,eAAe,EAAE,IAAI,CAC7B,CAAC,EAAE,KACD,EAAE,CAAC,KAAK,IAAI,IAAI;gBAChB,qBAAqB,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC;AACnD,oBAAA,qBAAqB,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CACpE,CACF,CAAC;SACH;AACD,QAAA,OAAO,OAAO,CAAC;KAChB,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC;IAEvC,OAAO;AACL,QAAA,OAAO,EAAE,eAAe;AACxB,QAAA,OAAO,EAAE,SAAS;AAClB,QAAA,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;KACpC,CAAC;AACJ;;;;"}
1
+ {"version":3,"file":"useDepositAddressOptions.js","sources":["../../../src/hooks/useDepositAddressOptions.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;AA+BA,MAAM,eAAe,GAAG;;AAEtB,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,aAAa;QAC9C,OAAO,EAAE,YAAY,CAAC,OAAO;AAC7B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,KAAK,EAAE,YAAY;AACpB,KAAA;AACD,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,aAAa;QAC9C,OAAO,EAAE,YAAY,CAAC,OAAO;AAC7B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,KAAK,EAAE,YAAY;AACpB,KAAA;;AAED,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,SAAS;QAC1C,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,IAAI,CAAC,OAAO;AACrB,QAAA,KAAK,EAAE,QAAQ;AAChB,KAAA;AACD,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,SAAS;QAC1C,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,IAAI,CAAC,OAAO;AACrB,QAAA,KAAK,EAAE,QAAQ;AAChB,KAAA;;AAED,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,WAAW;QAC5C,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,KAAK,EAAE,cAAc;AACtB,KAAA;AACD,IAAA;QACE,EAAE,EAAE,4BAA4B,CAAC,WAAW;QAC5C,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,cAAc,CAAC,OAAO;AAC/B,QAAA,KAAK,EAAE,cAAc;AACtB,KAAA;CACF,CAAC;AAEF;;;;;;;;;;;;AAYG;AACG,SAAU,wBAAwB,CAAC,EACvC,IAAI,EACJ,WAAW,EACX,IAAI,EACJ,SAAS,GACsB,EAAA;IAC/B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAwC;QACjF,OAAO,EAAE,WAAW,IAAI,IAAI,IAAI,WAAW,GAAG,CAAC,IAAI,IAAI,IAAI,IAAI;AAC/D,QAAA,QAAQ,EAAE,CAAC,uBAAuB,EAAE,WAAW,EAAE,IAAI,CAAC;QACtD,OAAO,EAAE,YAAW;AAClB,YAAA,IAAI;AACF,gBAAA,OAAO,MAAM,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC;oBAC/C,WAAW;oBACX,IAAI;AACL,iBAAA,CAAC,CAAC;aACJ;YAAC,OAAO,GAAG,EAAE;;AAEZ,gBAAA,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAC;AAC7D,gBAAA,OAAO,eAAe,CAAC;aACxB;SACF;AACD,QAAA,SAAS,EAAE,MAAM;AACjB,QAAA,KAAK,EAAE,CAAC;AACR,QAAA,oBAAoB,EAAE,KAAK;AAC5B,KAAA,CAAC,CAAC;;AAGH,IAAA,MAAM,eAAe,GAAG,OAAO,CAAC,MAAK;AACnC,QAAA,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;AAC3B,QAAA,IAAI,SAAS,EAAE,eAAe,IAAI,SAAS,EAAE,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE;;;;;;AAMvE,YAAA,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,IAAY,KAC9C,qBAAqB,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;AAC/C,YAAA,MAAM,SAAS,GAAG,IAAI,GAAG,CACvB,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAA,EAAG,EAAE,CAAC,OAAO,CAAA,CAAA,EAAI,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,CAAE,CAAA,CAAC,CAC1F,CAAC;AACF,YAAA,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,KAC3B,SAAS,CAAC,GAAG,CACX,CAAG,EAAA,MAAM,CAAC,KAAK,CAAC,OAAO,CAAA,CAAA,EAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAE,CAAA,CACjF,CACF,CAAC;SACH;AAED,QAAA,OAAO,OAAO,CAAC;KAChB,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC;IAEvC,OAAO;AACL,QAAA,OAAO,EAAE,eAAe;AACxB,QAAA,OAAO,EAAE,SAAS;AAClB,QAAA,KAAK,EAAE,KAAK;cACR,KAAK,YAAY,KAAK;kBACpB,KAAK,CAAC,OAAO;AACf,kBAAE,wCAAwC;AAC5C,cAAE,IAAI;KACT,CAAC;AACJ;;;;"}
@@ -1,10 +1,11 @@
1
- import { ExternalPaymentOptions, rozoStellar, TokenSymbol, assert, getPayment, checkoutPayment, buildCheckoutPayload, createPayment, debugJson, formatPaymentResponseToHydratedOrder, bscUSDT, assertNotNull, isValidSolanaAddress, rozoSolanaUSDC, rozoStellarUSDC, rozoStellarEURC, solana, rozoSolana, stellar, getChainById, getKnownToken, generateSolanaDeepLink, generateEVMDeepLink, writeRozoPayOrderID } from '@rozoai/intent-common';
1
+ import { ExternalPaymentOptions, rozoStellar, TokenSymbol, assert, isNativeToken, getPayment, checkoutPayment, buildCheckoutPayload, createPayment, debugJson, normalizeTokenAddress, formatPaymentResponseToHydratedOrder, bscUSDT, assertNotNull, isValidSolanaAddress, solanaSOL, stellarXLM, rozoStellarUSDC, rozoStellarEURC, solana, rozoSolana, stellar, FeeType, getChainById, getKnownToken, generateSolanaDeepLink, generateEVMDeepLink, writeRozoPayOrderID } from '@rozoai/intent-common';
2
2
  import { useWallet, useConnection } from '@solana/wallet-adapter-react';
3
- import { VersionedTransaction, PublicKey, Transaction } from '@solana/web3.js';
3
+ import { VersionedTransaction, PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
4
4
  import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
5
- import { parseUnits, zeroAddress, getAddress, erc20Abi, hexToBytes } from 'viem';
5
+ import { getAddress, erc20Abi, hexToBytes, parseUnits } from 'viem';
6
6
  import { useAccount, useSwitchChain, useSendTransaction, useWriteContract, useWalletClient, useCapabilities } from 'wagmi';
7
7
  import { useWriteContracts } from 'wagmi/experimental';
8
+ import { tokenBaseAmountToDecimalString, tokenAmountToBaseUnits } from '../utils/format.js';
8
9
  import { convertPreferredSymbolsToTokens } from '../utils/token.js';
9
10
  import { createMemoInstruction } from '@solana/spl-memo';
10
11
  import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, TOKEN_PROGRAM_ID, createTransferCheckedInstruction } from '@solana/spl-token';
@@ -52,7 +53,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
52
53
  const { data: walletClient } = useWalletClient();
53
54
  const { data: walletCapabilities, isPending: capabilitiesPending } = useCapabilities();
54
55
  const { writeContractsAsync } = useWriteContracts();
55
- // ponytail: order.metadata.dataSuffix survives ROZO_INVOICE_URL redirects where the
56
+ // order.metadata.dataSuffix survives ROZO_INVOICE_URL redirects where the
56
57
  // consumer's wagmi config (globalDataSuffix) is absent. Invoice checkout fetches the
57
58
  // order first and passes metadata.dataSuffix into getDefaultConfig, but this fallback
58
59
  // covers the in-SDK path where the order is already loaded.
@@ -161,12 +162,26 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
161
162
  }
162
163
  // payId mode: derive filtering params from the loaded order so that
163
164
  // payment option hooks apply the same token filtering as appId mode.
164
- // e.g. if destination is USDC, only show USDC/USDT sources (not EURC).
165
+ // e.g. if destination is USDC, allow paying with USDC/USDT/native tokens
166
+ // (we don't support payout to native tokens yet, but paying *with* native
167
+ // source balances is fine). EURC destinations are payable only with EURC.
165
168
  if (pay.order && pay.order.destFinalCallTokenAmount) {
166
169
  const destSymbol = pay.order.destFinalCallTokenAmount.token.symbol;
167
- // Derive preferredSymbol from destination: EURC destinations show EURC,
168
- // everything else defaults to USDC + USDT.
169
- const preferredSymbol = destSymbol === TokenSymbol.EURC ? [TokenSymbol.EURC] : [TokenSymbol.USDC, TokenSymbol.USDT];
170
+ // Derive preferredSymbol from destination: EURC destinations must be
171
+ // paid with EURC only. Everything else defaults to USDC + USDT plus
172
+ // native tokens as payable sources (payout is still fixed to
173
+ // toChain/toToken and never native).
174
+ const preferredSymbol = destSymbol === TokenSymbol.EURC
175
+ ? [TokenSymbol.EURC]
176
+ : [
177
+ TokenSymbol.USDC,
178
+ TokenSymbol.USDT,
179
+ TokenSymbol.ETH,
180
+ TokenSymbol.BNB,
181
+ TokenSymbol.POL,
182
+ TokenSymbol.SOL,
183
+ TokenSymbol.XLM,
184
+ ];
170
185
  const preferredTokens = convertPreferredSymbolsToTokens(preferredSymbol, undefined);
171
186
  return {
172
187
  toChain: pay.order.destFinalCallTokenAmount.token.chainId,
@@ -214,6 +229,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
214
229
  const depositAddressOptions = useDepositAddressOptions({
215
230
  trpc,
216
231
  usdRequired,
232
+ mode: pay.order?.mode,
217
233
  payParams: stablePayParams,
218
234
  });
219
235
  const chainOrderUsdLimits = useOrderUsdLimits({ trpc });
@@ -253,15 +269,14 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
253
269
  // Read from ref instead of closure to get latest value and avoid stale state
254
270
  const payParams = currPayParamsRef.current;
255
271
  const order = pay.order;
256
- // Narrow order to the specific shapes supported by buildCreatePaymentPayload.
257
- // In error/idle states `pay.order` can be a plain RozoPayOrder or null,
258
- // which is not assignable to the expected OrderLike type.
259
- const orderForPayload = order && "org" in order
260
- ? order
261
- : undefined;
262
272
  // If payment already exists (payId mode or rozoPaymentId set), checkout instead of create.
273
+ // backend forbids `checkout` when rotating to a native source token
274
+ // (SOL/ETH/XLM) — "create a new order instead" — so route native sources to the
275
+ // create branch below instead of calling checkout.
263
276
  const existingPayId = order?.externalId ?? rozoPaymentId ?? undefined;
264
- if (!payParams || existingPayId) {
277
+ const rotateToNative = walletOption != null && isNativeToken(walletOption.required.token.token);
278
+ const shouldCheckout = !!existingPayId && !rotateToNative;
279
+ if (!payParams || shouldCheckout) {
265
280
  if (!existingPayId) {
266
281
  throw new Error("No pay params provided");
267
282
  }
@@ -270,11 +285,20 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
270
285
  if (!paymentRes?.data) {
271
286
  throw new Error("Failed to fetch payment");
272
287
  }
288
+ // Use required.amount (base units, already priced by the backend)
289
+ // converted to a human-readable token amount — NOT required.usd,
290
+ // which only equals the token amount for 1:1-USD-pegged tokens
291
+ // (USDC/USDT). For native ETH/BNB/POL/SOL/XLM this would send the
292
+ // wrong amount to the backend (e.g. "1.00" instead of "0.0005").
293
+ // Some endpoints return required.amount as an already-human-readable
294
+ // decimal string instead of integer base units, so detect the shape
295
+ // first — BigInt() throws on decimal strings.
296
+ const sourceAmount = tokenBaseAmountToDecimalString(walletOption.required.amount, walletOption.required.token.decimals);
273
297
  const checkoutRes = await checkoutPayment(existingPayId, buildCheckoutPayload(paymentRes.data, {
274
298
  chainId: walletOption.required.token.chainId,
275
299
  tokenSymbol: walletOption.required.token.symbol,
276
300
  tokenAddress: walletOption.required.token.token,
277
- amount: String(walletOption.required.usd),
301
+ amount: sourceAmount,
278
302
  }));
279
303
  if (!checkoutRes?.data) {
280
304
  throw new Error("Failed to checkout payment");
@@ -293,6 +317,14 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
293
317
  }
294
318
  }
295
319
  try {
320
+ // Narrow order to the specific shapes supported by buildCreatePaymentPayload.
321
+ // In error/idle states `pay.order` can be a plain RozoPayOrder or null,
322
+ // which lacks `destFinalCallTokenAmount`; passing it through would throw
323
+ // when we derive the destination amount. Fall back to the payParams-only
324
+ // branch (order undefined) in that case.
325
+ const orderForPayload = order && "org" in order
326
+ ? order
327
+ : undefined;
296
328
  const payload = buildCreatePaymentPayload({
297
329
  payParams,
298
330
  order: orderForPayload,
@@ -333,22 +365,34 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
333
365
  if (!validatePaymentRequirements(walletOption, pay.paymentState)) {
334
366
  throw new Error(`[PAY TOKEN] required token ${debugJson(required)} does not match fees token ${debugJson(fees)}`);
335
367
  }
336
- // @NOTE: Fee handled by Rozo API
337
- // const paymentAmount = BigInt(required.amount) + BigInt(fees.amount);
338
- const paymentAmount = parseUnits(required.usd.toString(), required.token.decimals);
368
+ // Use required.amount (base units, already priced by the backend) —
369
+ // NOT required.usd, which only equals the token amount for 1:1-USD-pegged
370
+ // tokens (USDC/USDT). For native ETH/BNB/POL this would send the wrong
371
+ // amount (e.g. 1.00 ETH instead of ~0.0006 ETH for a $1 payment).
372
+ const paymentAmount = tokenAmountToBaseUnits(required.amount, required.token.decimals);
339
373
  // Check if we need to create a new Rozo payment (cache this check)
340
374
  const previousChainId = pay.order.preferredChainId ? Number(pay.order.preferredChainId) : null;
375
+ const previousTokenAddress = pay.order.preferredTokenAddress;
341
376
  // If we have an existing payment and chain differs, MUST create new payment
342
377
  // Also check if order has externalId (existing payment) and we're on different chain
343
378
  const hasExistingPayment = pay.order.externalId != null;
379
+ // Check both chain AND token change - if user switches token on same chain,
380
+ // we still need to checkout to update source token info
381
+ const tokenChanged = previousTokenAddress != null &&
382
+ normalizeTokenAddress(Number(previousChainId), previousTokenAddress) !==
383
+ normalizeTokenAddress(required.token.chainId, required.token.token);
344
384
  const needRozoPayment = (previousChainId !== null && previousChainId !== required.token.chainId) ||
385
+ tokenChanged ||
345
386
  (hasExistingPayment &&
346
387
  previousChainId === null &&
347
388
  pay.order.destFinalCallTokenAmount?.token?.chainId !== required.token.chainId);
348
- log?.(`[PAY TOKEN] Chain check: previous=${previousChainId}, current=${required.token.chainId}, hasExistingPayment=${hasExistingPayment}, needRozoPayment=${needRozoPayment}`);
389
+ log?.(`[PAY TOKEN] Chain/token check: previousChain=${previousChainId}, currentChain=${required.token.chainId}, previousToken=${previousTokenAddress}, currentToken=${required.token.token}, tokenChanged=${tokenChanged}, hasExistingPayment=${hasExistingPayment}, needRozoPayment=${needRozoPayment}`);
349
390
  // Prepare transaction parameters early (before async operations)
350
- const isNativeToken = required.token.token === zeroAddress;
351
- const tokenAddress = isNativeToken ? null : getAddress(required.token.token);
391
+ // Case-insensitive native detection: `required.token.token` is checksummed
392
+ // (from the API) while viem's `ethAddress` is lowercase, so a direct `===`
393
+ // would misclassify native ETH as an ERC20 and take the wrong transfer path.
394
+ const isNative = isNativeToken(required.token.token);
395
+ const tokenAddress = isNative ? null : getAddress(required.token.token);
352
396
  // Get hydrated order efficiently with parallel preparation
353
397
  let hydratedOrder;
354
398
  let paymentId;
@@ -445,7 +489,14 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
445
489
  // Execute transaction with optimized error handling
446
490
  const paymentTxHash = await (async () => {
447
491
  try {
448
- if (isNativeToken) {
492
+ if (required.token.chainId !== bscUSDT.chainId) {
493
+ await switchChainAsync({ chainId: required.token.chainId });
494
+ }
495
+ log?.(`[PAY ERC20] dataSuffix: ${resolvedDataSuffix ?? "(none)"}`);
496
+ // EIP-5792 path: only when wallet advertises dataSuffix capability (Base App / Coinbase Wallet).
497
+ const chainCapabilities = walletCapabilities?.[required.token.chainId];
498
+ const supportsDataSuffix = !capabilitiesPending && resolvedDataSuffix != null && !!chainCapabilities?.dataSuffix;
499
+ if (isNative) {
449
500
  // dataSuffix intentionally omitted — appending data to bare ETH transfers
450
501
  // changes wallet UI display; builder-code attribution targets contract calls.
451
502
  return await sendTransactionAsync({
@@ -454,13 +505,6 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
454
505
  });
455
506
  }
456
507
  else {
457
- if (required.token.chainId !== bscUSDT.chainId) {
458
- await switchChainAsync({ chainId: required.token.chainId });
459
- }
460
- log?.(`[PAY ERC20] dataSuffix: ${resolvedDataSuffix ?? "(none)"}`);
461
- // EIP-5792 path: only when wallet advertises dataSuffix capability (Base App / Coinbase Wallet).
462
- const chainCapabilities = walletCapabilities?.[required.token.chainId];
463
- const supportsDataSuffix = !capabilitiesPending && resolvedDataSuffix != null && !!chainCapabilities?.dataSuffix;
464
508
  if (supportsDataSuffix) {
465
509
  if (!walletClient)
466
510
  throw new Error("No walletClient available");
@@ -593,11 +637,9 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
593
637
  throw new Error(`Invalid Solana destination address format: ${rozoPayment.destAddress}`);
594
638
  }
595
639
  // Set up token addresses
596
- let mintAddress;
597
640
  let fromKey;
598
641
  let toKey;
599
642
  try {
600
- mintAddress = new PublicKey(walletPaymentOption.required.token.token);
601
643
  fromKey = new PublicKey(payerPublicKey);
602
644
  toKey = new PublicKey(rozoPayment.destAddress);
603
645
  }
@@ -605,32 +647,77 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
605
647
  throw new Error(`Invalid Solana address format: ${error.message}. ` +
606
648
  `Destination address: ${rozoPayment.destAddress}`);
607
649
  }
608
- log("[PAY SOLANA] Transaction details:", {
609
- tokenMint: mintAddress.toString(),
610
- fromKey: fromKey.toString(),
611
- toKey: toKey.toString(),
612
- amount: walletPaymentOption.required.usd,
613
- memo: rozoPayment.memo,
614
- });
615
- // Get token accounts for sender and recipient
616
- log("[PAY SOLANA] Deriving associated token accounts...");
617
- const senderTokenAccount = await getAssociatedTokenAddress(mintAddress, fromKey);
618
- const recipientTokenAccount = await getAssociatedTokenAddress(mintAddress, toKey);
619
- log("[PAY SOLANA] Sender token account:", senderTokenAccount.toString());
620
- log("[PAY SOLANA] Recipient token account:", recipientTokenAccount.toString());
621
- // Check if recipient token account exists
622
- log("[PAY SOLANA] Checking if recipient token account exists...");
623
- const recipientTokenInfo = await connection.getAccountInfo(recipientTokenAccount);
624
- // Create recipient token account if it doesn't exist
625
- if (!recipientTokenInfo) {
626
- log("[PAY SOLANA] Creating recipient token account...");
627
- instructions.push(createAssociatedTokenAccountInstruction(payerPublicKey, recipientTokenAccount, toKey, mintAddress, TOKEN_PROGRAM_ID));
650
+ const isNativeSol = walletPaymentOption.required.token.token === solanaSOL.token;
651
+ if (isNativeSol) {
652
+ if (!rozoPayment.sourceAmount) {
653
+ throw new Error("Source amount is required for native SOL payment");
654
+ }
655
+ // Native SOL: plain lamport transfer, no token account / mint involved.
656
+ // Use required.amount (already computed by the backend in lamports,
657
+ // correctly priced from USD via the live SOL/USD rate) instead of
658
+ // re-deriving from required.usd, which would incorrectly treat SOL
659
+ // as pegged 1:1 to USD.
660
+ log("[PAY SOLANA] Transaction details (native SOL):", {
661
+ fromKey: fromKey.toString(),
662
+ toKey: toKey.toString(),
663
+ amount: walletPaymentOption.required.amount,
664
+ usd: walletPaymentOption.required.usd,
665
+ memo: rozoPayment.memo,
666
+ });
667
+ // Keep as bigint — SystemProgram.transfer accepts number | bigint.
668
+ // Converting to Number would lose precision for amounts > 2^53 base units.
669
+ const lamports = tokenAmountToBaseUnits(walletPaymentOption.required.amount, solanaSOL.decimals);
670
+ log("[PAY SOLANA] Transfer amount (lamports):", lamports.toString());
671
+ instructions.push(SystemProgram.transfer({
672
+ fromPubkey: fromKey,
673
+ toPubkey: toKey,
674
+ lamports: parseUnits(rozoPayment.sourceAmount, solanaSOL.decimals),
675
+ }));
676
+ }
677
+ else {
678
+ let mintAddress;
679
+ try {
680
+ mintAddress = new PublicKey(walletPaymentOption.required.token.token);
681
+ }
682
+ catch (error) {
683
+ throw new Error(`Invalid Solana address format: ${error.message}. ` +
684
+ `Destination address: ${rozoPayment.destAddress}`);
685
+ }
686
+ log("[PAY SOLANA] Transaction details:", {
687
+ tokenMint: mintAddress.toString(),
688
+ fromKey: fromKey.toString(),
689
+ toKey: toKey.toString(),
690
+ amount: walletPaymentOption.required.amount,
691
+ usd: walletPaymentOption.required.usd,
692
+ memo: rozoPayment.memo,
693
+ });
694
+ // Get token accounts for sender and recipient
695
+ log("[PAY SOLANA] Deriving associated token accounts...");
696
+ const senderTokenAccount = await getAssociatedTokenAddress(mintAddress, fromKey);
697
+ const recipientTokenAccount = await getAssociatedTokenAddress(mintAddress, toKey);
698
+ log("[PAY SOLANA] Sender token account:", senderTokenAccount.toString());
699
+ log("[PAY SOLANA] Recipient token account:", recipientTokenAccount.toString());
700
+ // Check if recipient token account exists
701
+ log("[PAY SOLANA] Checking if recipient token account exists...");
702
+ const recipientTokenInfo = await connection.getAccountInfo(recipientTokenAccount);
703
+ // Create recipient token account if it doesn't exist
704
+ if (!recipientTokenInfo) {
705
+ log("[PAY SOLANA] Creating recipient token account...");
706
+ instructions.push(createAssociatedTokenAccountInstruction(payerPublicKey, recipientTokenAccount, toKey, mintAddress, TOKEN_PROGRAM_ID));
707
+ }
708
+ // Add transfer instruction
709
+ // Use required.amount (already computed by the backend in the
710
+ // token's base units, correctly priced from USD) instead of
711
+ // re-deriving from required.usd with a hardcoded 6-decimal
712
+ // assumption, which silently breaks for non-1:1-USD or
713
+ // differently-decimaled SPL tokens.
714
+ log("[PAY SOLANA] Adding transfer instruction...");
715
+ // Keep as bigint — createTransferCheckedInstruction accepts number | bigint.
716
+ // Converting to Number would lose precision for amounts > 2^53 base units.
717
+ const transferAmount = tokenAmountToBaseUnits(walletPaymentOption.required.amount, walletPaymentOption.required.token.decimals);
718
+ log("[PAY SOLANA] Transfer amount (base units):", transferAmount.toString());
719
+ instructions.push(createTransferCheckedInstruction(senderTokenAccount, mintAddress, recipientTokenAccount, fromKey, transferAmount, walletPaymentOption.required.token.decimals));
628
720
  }
629
- // Add transfer instruction
630
- log("[PAY SOLANA] Adding transfer instruction...");
631
- const transferAmount = parseFloat(walletPaymentOption.required.usd.toString()) * 1_000_000;
632
- log("[PAY SOLANA] Transfer amount (with decimals):", transferAmount);
633
- instructions.push(createTransferCheckedInstruction(senderTokenAccount, mintAddress, recipientTokenAccount, fromKey, transferAmount, rozoSolanaUSDC.decimals));
634
721
  // Add memo if provided
635
722
  if (rozoPayment.memo) {
636
723
  log("[PAY SOLANA] Adding memo instruction:", rozoPayment.memo);
@@ -693,18 +780,29 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
693
780
  await setStellarWallet(stellarConnector);
694
781
  }
695
782
  const sourceAccount = await stellarServer.loadAccount(stellarPublicKey);
696
- let issuer = "";
697
- if (walletPaymentOption.required.token.token === rozoStellarUSDC.token) {
698
- issuer = rozoStellarUSDC.token.split(":")[1];
783
+ let destAsset;
784
+ if (walletPaymentOption.required.token.token === stellarXLM.token) {
785
+ destAsset = Asset.native();
786
+ }
787
+ else if (walletPaymentOption.required.token.token === rozoStellarUSDC.token) {
788
+ destAsset = new Asset(walletPaymentOption.required.token.symbol, rozoStellarUSDC.token.split(":")[1]);
699
789
  }
700
790
  else if (walletPaymentOption.required.token.token === rozoStellarEURC.token) {
701
- issuer = rozoStellarEURC.token.split(":")[1];
791
+ destAsset = new Asset(walletPaymentOption.required.token.symbol, rozoStellarEURC.token.split(":")[1]);
702
792
  }
703
793
  else {
704
794
  throw new Error("Unsupported token");
705
795
  }
706
- const destAsset = new Asset(walletPaymentOption.required.token.symbol, issuer);
707
796
  const fee = String(await stellarServer.fetchBaseFee());
797
+ // Use required.amount (base units, already priced by the backend)
798
+ // converted to a human-readable token amount — NOT required.usd,
799
+ // which only equals the token amount for 1:1-USD-pegged tokens
800
+ // (USDC/EURC). For native XLM this would send the wrong amount
801
+ // (e.g. "1.00" XLM instead of the correct ~2.7 XLM for a $1 payment).
802
+ // Some endpoints return required.amount as an already-human-readable
803
+ // decimal string instead of integer base units, so detect the shape
804
+ // first — BigInt() throws on decimal strings.
805
+ const paymentAmount = tokenBaseAmountToDecimalString(walletPaymentOption.required.amount, walletPaymentOption.required.token.decimals);
708
806
  // Build transaction
709
807
  const transaction = new TransactionBuilder(sourceAccount, {
710
808
  fee,
@@ -713,7 +811,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
713
811
  .addOperation(Operation.payment({
714
812
  destination: destinationAddress,
715
813
  asset: destAsset,
716
- amount: String(walletPaymentOption.required.usd),
814
+ amount: paymentAmount,
717
815
  }))
718
816
  .setTimeout(180);
719
817
  if (rozoPayment.memo) {
@@ -788,17 +886,29 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
788
886
  }
789
887
  else {
790
888
  log?.("[PAY DEPOSIT ADDRESS] hydrating order");
889
+ // hydrateOrder needs required.amount so buildCreatePaymentPayload
890
+ // can set preferredAmountUnits. For BOTH native (SOL/ETH/XLM) and
891
+ // 1:1-USD-pegged sources, the amount must come from the fee response
892
+ // (BE-computed source amount in source units, fee-inclusive) — NOT the
893
+ // destination USD value. Otherwise tokenBaseAmountToDecimalString
894
+ // formats the toUnits through the source decimals and the BE rejects
895
+ // the underflowing pay-in (e.g. "10" with 6 decimals → "0.00001").
896
+ const isNativeSource = isNativeToken(option.token.token);
897
+ const sourceAmountUnits = String(fees?.source?.amount ??
898
+ (isNativeSource ? "" : (pay.order?.destFinalCallTokenAmount?.usd ?? "0")));
899
+ if (isNativeSource && sourceAmountUnits === "") {
900
+ throw new Error("Fee source amount required for native token deposit");
901
+ }
902
+ const actualFeeType = payParams?.feeType ?? FeeType.ExactIn;
791
903
  const result = await pay.hydrateOrder(undefined, {
792
904
  required: {
793
- token: {
794
- token: option.token.token,
795
- chainId: option.token.chainId,
796
- },
905
+ amount: sourceAmountUnits,
906
+ token: option.token,
797
907
  },
798
908
  fees: {
799
909
  usd: fees?.source?.fee != null ? parseFloat(fees.source.fee) : 0,
800
910
  },
801
- });
911
+ }, actualFeeType);
802
912
  order = result.order;
803
913
  }
804
914
  log?.(`[PAY DEPOSIT ADDRESS] hydrated order ${order.id} for ${order.usdValue} USD, checking out with deposit address: ${option}`);
@@ -815,12 +925,23 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
815
925
  if (!preferredToken) {
816
926
  throw new Error("Preferred token not found");
817
927
  }
928
+ // Amount the user must send, in source-token units. Prefer the backend's
929
+ // source.amount (surfaced via metadata by formatPaymentResponseToHydratedOrder)
930
+ // over usdValue: for native sources (SOL/ETH/XLM) the source amount differs
931
+ // from the USD/destination value, so usdValue would show e.g. "1.3 SOL"
932
+ // instead of the correct ~0.0169 SOL. For 1:1-USD sources they coincide.
933
+ const isNativeDepositSource = isNativeToken(option.token.token);
934
+ const metaSourceAmount = order.metadata?.sourceAmountUnits;
935
+ if (isNativeDepositSource && metaSourceAmount == null) {
936
+ throw new Error("sourceAmountUnits missing on hydrated order for native deposit source");
937
+ }
938
+ const sourceAmountUnits = metaSourceAmount != null ? String(metaSourceAmount) : String(order.usdValue);
818
939
  let uriDeeplink = null;
819
940
  // Use Solana deep link if it's a Solana chain.
820
941
  // Solana pay-in no longer requires a memo.
821
942
  if ([solana.chainId, rozoSolana.chainId].includes(preferredToken.chainId)) {
822
943
  uriDeeplink = generateSolanaDeepLink({
823
- amountUnits: order.destFinalCallTokenAmount.usd.toString(),
944
+ amountUnits: sourceAmountUnits,
824
945
  recipientAddress: order.intentAddr,
825
946
  tokenAddress: preferredToken.token,
826
947
  });
@@ -832,7 +953,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
832
953
  // Otherwise use EVM deep link
833
954
  else {
834
955
  uriDeeplink = generateEVMDeepLink({
835
- amountUnits: parseUnits(order.destFinalCallTokenAmount.usd.toString(), preferredToken.decimals).toString(),
956
+ amountUnits: parseUnits(sourceAmountUnits, preferredToken.decimals).toString(),
836
957
  chainId: preferredToken.chainId,
837
958
  recipientAddress: order.intentAddr,
838
959
  tokenAddress: preferredToken.token,
@@ -840,7 +961,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
840
961
  }
841
962
  return {
842
963
  address: order.intentAddr,
843
- amount: String(order.usdValue),
964
+ amount: sourceAmountUnits,
844
965
  suffix: `${option.token.symbol} ${chain.name}`,
845
966
  uri: uriDeeplink ?? "",
846
967
  expirationS: Math.floor(Date.now() / 1000) + 300,
@@ -855,7 +976,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
855
976
  order: pay.order,
856
977
  message,
857
978
  });
858
- return null;
979
+ return undefined;
859
980
  }
860
981
  finally {
861
982
  // Remove from processing set when done (allow retries after completion/failure)