@rango-dev/widget-embedded 0.0.0-experimental-e022ed95-20260421 → 0.0.0-experimental-e79b998c-20260503

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rango-dev/widget-embedded",
3
- "version": "0.0.0-experimental-e022ed95-20260421",
3
+ "version": "0.0.0-experimental-e79b998c-20260503",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "source": "./src/index.ts",
@@ -25,23 +25,23 @@
25
25
  "@lingui/core": "4.2.1",
26
26
  "@lingui/react": "4.2.1",
27
27
  "@rango-dev/logging-core": "^0.12.1",
28
- "@rango-dev/provider-all": "^0.0.0-experimental-e022ed95-20260421",
28
+ "@rango-dev/provider-all": "^0.61.1",
29
29
  "@rango-dev/queue-manager-core": "^0.33.0",
30
- "@rango-dev/queue-manager-rango-preset": "^0.60.0",
30
+ "@rango-dev/queue-manager-rango-preset": "^0.61.1",
31
31
  "@rango-dev/queue-manager-react": "^0.33.0",
32
- "@rango-dev/signer-solana": "^0.0.0-experimental-e022ed95-20260421",
33
- "@rango-dev/ui": "^0.61.0",
34
- "@rango-dev/wallets-core": "^0.57.0",
35
- "@rango-dev/wallets-react": "^0.44.0",
36
- "@rango-dev/wallets-shared": "^0.58.0",
32
+ "@rango-dev/signer-solana": "^0.47.1",
33
+ "@rango-dev/ui": "^0.0.0-experimental-e79b998c-20260503",
34
+ "@rango-dev/wallets-core": "^0.58.1",
35
+ "@rango-dev/wallets-react": "^0.45.1",
36
+ "@rango-dev/wallets-shared": "^0.59.1",
37
37
  "bignumber.js": "^9.1.1",
38
38
  "copy-to-clipboard": "^3.3.3",
39
39
  "dayjs": "^1.11.7",
40
40
  "ethers": "^6.13.2",
41
41
  "immer": "^9.0.19",
42
42
  "mitt": "^3.0.0",
43
- "rango-sdk": "^0.1.70",
44
- "rango-types": "^0.1.89",
43
+ "rango-sdk": "^0.1.77",
44
+ "rango-types": "^0.1.98",
45
45
  "react-i18next": "^12.2.0",
46
46
  "react-router-dom": "^6.8.0",
47
47
  "values.js": "2.1.1",
@@ -1,6 +1,5 @@
1
1
  import { UI_ID } from '@rango-dev/ui';
2
2
 
3
- export const RANGO_PUBLIC_API_KEY = 'c6381a79-2817-4602-83bf-6a641a409e32';
4
3
  export const DEFAULT_BASE_URL = 'https://api.rango.exchange';
5
4
  export const SCANNER_BASE_URL = 'https://explorer.rango.exchange';
6
5
 
@@ -116,14 +116,9 @@ export function Inputs(props: PropTypes) {
116
116
  loadingBalance={fetchingBalance}
117
117
  tooltipContainer={getContainer()}
118
118
  onSelectMaxBalance={() => {
119
- const tokenBalanceReal = numberToString(
120
- fromBalanceAmount,
121
- fromTokenBalance?.decimals
122
- );
123
-
124
119
  // if a token hasn't any value, we will reset the input by setting an empty string.
125
- const nextInputAmount = !!fromTokenBalance?.amount
126
- ? tokenBalanceReal.split(',').join('')
120
+ const nextInputAmount = fromBalanceAmount.isGreaterThan(ZERO)
121
+ ? fromBalanceAmount.toFixed()
127
122
  : '';
128
123
 
129
124
  setInputAmount(nextInputAmount);
@@ -4,11 +4,12 @@ import type { PropsWithChildren } from 'react';
4
4
  import { setSolanaSignerConfig } from '@rango-dev/signer-solana';
5
5
  import React, { useEffect, useMemo } from 'react';
6
6
 
7
- import { DEFAULT_BASE_URL, RANGO_PUBLIC_API_KEY } from '../../constants';
7
+ import { DEFAULT_BASE_URL } from '../../constants';
8
8
  import { useFetchApiConfig } from '../../hooks/useFetchApiConfig';
9
9
  import useFontLoader from '../../hooks/useFontLoader';
10
10
  import QueueManager from '../../QueueManager';
11
11
  import { initConfig } from '../../utils/configs';
12
+ import { getApiKeyFromEnvOrThrow } from '../../utils/env';
12
13
  import { WidgetWallets } from '../Wallets';
13
14
  import { WidgetInfo } from '../WidgetInfo';
14
15
 
@@ -32,7 +33,7 @@ export function WidgetProvider(props: PropsWithChildren<PropTypes>) {
32
33
 
33
34
  useMemo(() => {
34
35
  initConfig({
35
- API_KEY: config?.apiKey || RANGO_PUBLIC_API_KEY,
36
+ API_KEY: config?.apiKey || getApiKeyFromEnvOrThrow(),
36
37
  BASE_URL: config?.apiUrl || DEFAULT_BASE_URL,
37
38
  });
38
39
  }, [config.apiKey, config.apiUrl]);
@@ -26,6 +26,7 @@ import {
26
26
  import { isPositiveNumber, sanitizeInputAmount } from '../utils/numbers';
27
27
  import {
28
28
  ensureLeadingZeroForDecimal,
29
+ parseNumericValue,
29
30
  removeLeadingZeros,
30
31
  } from '../utils/sanitizers';
31
32
  import { getUsdInputFrom, getUsdOutputFrom } from '../utils/swap';
@@ -249,7 +250,7 @@ const initializer: StateCreator<
249
250
  }));
250
251
  },
251
252
  setInputAmount: (amount) => {
252
- let sanitized = amount;
253
+ let sanitized = parseNumericValue(amount);
253
254
  if (!isZeroValue(amount)) {
254
255
  // sanitize once a meaningful digit is entered (e.g. "00001" → "1")
255
256
  sanitized = removeLeadingZeros(sanitized);
@@ -466,7 +466,7 @@ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
466
466
  const { _balances, _aggregatedBalances } =
467
467
  computeNextStateAfterWalletBalanceRemoval(
468
468
  partialCurrentState,
469
- walletType,
469
+ [walletType],
470
470
  options
471
471
  );
472
472
 
@@ -611,8 +611,10 @@ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
611
611
  if (accounts.length === 0 || !accounts[0]) {
612
612
  return;
613
613
  }
614
- // All the `accounts` have same `walletType` so we can pick the first one.
615
- const walletType = accounts[0].walletType;
614
+
615
+ const walletTypes = [
616
+ ...new Set(accounts.map((account) => account.walletType)),
617
+ ];
616
618
 
617
619
  get().setConnectedWalletAsRefetching(accounts);
618
620
 
@@ -630,7 +632,6 @@ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
630
632
  return;
631
633
  }
632
634
  const walletsDetails = response.wallets;
633
-
634
635
  if (walletsDetails) {
635
636
  let nextBalances: BalanceState = get()._balances;
636
637
  let nextAggregatedBalances: AggregatedBalanceState =
@@ -657,7 +658,7 @@ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
657
658
  const { _balances, _aggregatedBalances } =
658
659
  computeNextStateAfterWalletBalanceRemoval(
659
660
  partialCurrentState,
660
- walletType,
661
+ walletTypes,
661
662
  {
662
663
  chains: [wallet.blockChain],
663
664
  }
@@ -672,7 +673,7 @@ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
672
673
  if (
673
674
  !get().connectedWallets.find(
674
675
  (connectedWallet) =>
675
- connectedWallet.walletType === walletType &&
676
+ walletTypes.includes(connectedWallet.walletType) &&
676
677
  connectedWallet.address === wallet.address &&
677
678
  connectedWallet.chain === wallet.blockChain
678
679
  )
@@ -715,11 +716,23 @@ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
715
716
  if (retryOnFailedBalances) {
716
717
  const failedWallets: Wallet[] = walletsDetails
717
718
  .filter((wallet) => wallet.failed)
718
- .map((wallet) => ({
719
- chain: wallet.blockChain,
720
- walletType: walletType,
721
- address: wallet.address,
722
- }));
719
+ .map((wallet) => {
720
+ const walletType = get().connectedWallets.find(
721
+ (connectedWallet) =>
722
+ connectedWallet.chain === wallet.blockChain &&
723
+ connectedWallet.address
724
+ )?.walletType;
725
+ // Return null if there is no related connected wallet.
726
+ if (!walletType) {
727
+ return null;
728
+ }
729
+ return {
730
+ chain: wallet.blockChain,
731
+ walletType: walletType,
732
+ address: wallet.address,
733
+ };
734
+ })
735
+ .filter((wallet) => !!wallet);
723
736
  if (failedWallets.length > 0) {
724
737
  await get().fetchMainTokensBalances(failedWallets, {
725
738
  retryOnFailedBalances: false,
@@ -164,14 +164,14 @@ export function computeNextStateAfterWalletBalanceRemoval(
164
164
  _aggregatedBalances: AggregatedBalanceState;
165
165
  connectedWallets: ConnectedWallet[];
166
166
  },
167
- walletType: string,
167
+ walletTypes: string[],
168
168
  options?: {
169
169
  chains?: string[];
170
170
  namespaces?: Namespace[];
171
171
  }
172
172
  ) {
173
173
  let walletsNeedsToBeRemoved = paritalCurrentState.connectedWallets.filter(
174
- (connectedWallet) => connectedWallet.walletType === walletType
174
+ (connectedWallet) => walletTypes.includes(connectedWallet.walletType)
175
175
  );
176
176
 
177
177
  /*
@@ -181,7 +181,7 @@ export function computeNextStateAfterWalletBalanceRemoval(
181
181
  * So we only delete balance when there is no connected wallet that has access to that specific chain and address.
182
182
  */
183
183
  paritalCurrentState.connectedWallets.forEach((connectedWallet) => {
184
- if (connectedWallet.walletType !== walletType) {
184
+ if (!walletTypes.includes(connectedWallet.walletType)) {
185
185
  walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
186
186
  const isAnotherWalletHasSameAddressAndChain =
187
187
  wallet.chain === connectedWallet.chain &&
@@ -1,8 +1,7 @@
1
1
  import type { Tokens, WidgetVariant } from '../types';
2
2
  import type { Asset, BlockchainMeta, Token } from 'rango-sdk';
3
3
 
4
- import { RANGO_PUBLIC_API_KEY } from '../constants';
5
-
4
+ import { getApiKeyFromEnvOrNotSet } from './env';
6
5
  import { areTokensEqual } from './wallets';
7
6
 
8
7
  export interface Configs {
@@ -17,7 +16,7 @@ type TokensConfig =
17
16
  };
18
17
 
19
18
  let configs: Configs = {
20
- API_KEY: RANGO_PUBLIC_API_KEY,
19
+ API_KEY: getApiKeyFromEnvOrNotSet(),
21
20
  };
22
21
 
23
22
  export function getConfig(name: keyof Configs) {
@@ -0,0 +1,28 @@
1
+ export function getApiKeyFromEnv(): string | undefined {
2
+ // NOTE: parcel doesn't support for dynamic keys, so we have to do it statically.
3
+ return process.env.REACT_APP_RANGO_API_KEY;
4
+ }
5
+
6
+ /**
7
+ * This is useful when the api key is mandatory and nothing works corectly without api key.
8
+ * You need to first fallback to user's input (widget config), then you can use this to try to find from envs (e.g. widget/app or widget/playground)
9
+ * Then throw an runtime error to panic the program.
10
+ */
11
+ export function getApiKeyFromEnvOrThrow() {
12
+ // NOTE: parcel doesn't support for dynamic keys, so we have to do it statically.
13
+ const value = getApiKeyFromEnv();
14
+ if (value) {
15
+ return value;
16
+ }
17
+
18
+ throw new Error(
19
+ 'Ensure you have set REACT_APP_RANGO_API_KEY in your env variables.'
20
+ );
21
+ }
22
+
23
+ /**
24
+ * You can use this whenever you want to use the api key in initialize process, but mark the key explictly as not set to debug more easily since you should never reach there.
25
+ */
26
+ export function getApiKeyFromEnvOrNotSet(): string {
27
+ return getApiKeyFromEnv() || 'NOT_SET';
28
+ }
@@ -134,5 +134,9 @@ export function sanitizeInputAmount(amount: string): string {
134
134
  return '0';
135
135
  }
136
136
 
137
+ if (amount.endsWith('.')) {
138
+ return amount.slice(0, -1);
139
+ }
140
+
137
141
  return stripTrailingZeros(amount);
138
142
  }
@@ -4,6 +4,7 @@ import { describe, expect, test } from 'vitest';
4
4
  import {
5
5
  ensureLeadingZeroForDecimal,
6
6
  formatThousandsWithCommas,
7
+ parseNumericValue,
7
8
  removeLeadingZeros,
8
9
  replaceSpacesWithDash,
9
10
  stripTrailingZeros,
@@ -119,4 +120,46 @@ describe('check sanitization behaviors', () => {
119
120
  expect(stripTrailingZeros('1000')).toBe('1000');
120
121
  });
121
122
  });
123
+
124
+ describe('parseNumericValue', () => {
125
+ test('removes commas and keeps digits', () => {
126
+ expect(parseNumericValue('300,222')).toBe('300222');
127
+ });
128
+
129
+ test('removes letters and keeps only the first decimal', () => {
130
+ expect(parseNumericValue('12a3.4b5')).toBe('123.45');
131
+ });
132
+
133
+ test('removes extra dots and keeps the first one', () => {
134
+ expect(parseNumericValue('1.2.3.4')).toBe('1.234');
135
+ });
136
+
137
+ test('keeps a leading decimal', () => {
138
+ expect(parseNumericValue('.5')).toBe('.5');
139
+ });
140
+
141
+ test('removes symbols and letters', () => {
142
+ expect(parseNumericValue('$1,2a3.4!')).toBe('123.4');
143
+ });
144
+
145
+ test('returns empty string if no digits', () => {
146
+ expect(parseNumericValue('abc!@#')).toBe('');
147
+ });
148
+
149
+ test('handles empty string', () => {
150
+ expect(parseNumericValue('')).toBe('');
151
+ });
152
+
153
+ test('keeps only the first dot if string starts with multiple dots', () => {
154
+ expect(parseNumericValue('...123.45')).toBe('.12345');
155
+ });
156
+
157
+ test('handles string with only dots', () => {
158
+ expect(parseNumericValue('....')).toBe('.');
159
+ });
160
+
161
+ test('handles string with digits only', () => {
162
+ expect(parseNumericValue('123456')).toBe('123456');
163
+ });
164
+ });
122
165
  });
@@ -39,3 +39,29 @@ export function stripTrailingZeros(input: string): string {
39
39
  const s = input.replace(/(\.\d*?[1-9])0+$/, '$1');
40
40
  return s.replace(/\.0+$/, '');
41
41
  }
42
+
43
+ /**
44
+ * Normalize a numeric input string by removing all non-numeric characters
45
+ * except digits and a single decimal separator.
46
+ *
47
+ * This function:
48
+ * - strips letters, spaces, commas, and symbols
49
+ * - preserves digits (`0–9`)
50
+ * - keeps only the first `.` as the decimal point
51
+ *
52
+ * @example "300,222" → "300222"
53
+ * @example "12a3.4b5" → "123.45"
54
+ * @example "1.2.3.4" → "1.234"
55
+ * @example ".5" → ".5"
56
+ */
57
+ export function parseNumericValue(value: string): string {
58
+ value = value
59
+ // 1. Remove everything except digits and dots
60
+ .replace(/[^\d.]/g, '')
61
+ // 2. Keep only the first dot
62
+ .replace(/\./g, (_, offset, string) =>
63
+ string.indexOf('.') === offset ? '.' : ''
64
+ );
65
+
66
+ return value;
67
+ }