@xswap-link/sdk 0.12.2 → 0.13.1
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/.github/workflows/main.yml +4 -4
- package/.github/workflows/publish.yml +4 -4
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.global.js +305 -5448
- package/dist/index.js +308 -1248
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +305 -1245
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +6 -25
- package/src/context/SwapProvider.tsx +104 -70
- package/src/contracts/abi/index.ts +0 -2
- package/src/contracts/addresses.ts +0 -4
- package/src/contracts/bridgeDisplayTokens.ts +46 -0
- package/src/contracts/index.ts +1 -0
- package/src/models/Addresses.ts +0 -1
- package/src/contracts/abi/CustomXSwapRouter.json +0 -953
package/package.json
CHANGED
|
@@ -11,11 +11,7 @@ import {
|
|
|
11
11
|
crosschainEstimationTimes,
|
|
12
12
|
} from "@src/constants";
|
|
13
13
|
import { useHistory, useSwapContext, useTxUIWrapper } from "@src/context";
|
|
14
|
-
import {
|
|
15
|
-
ADDRESSES,
|
|
16
|
-
CustomXSwapRouterAbi,
|
|
17
|
-
XSwapRouterAbi,
|
|
18
|
-
} from "@src/contracts";
|
|
14
|
+
import { ADDRESSES, XSwapRouterAbi } from "@src/contracts";
|
|
19
15
|
import { useEvmContractApi } from "@src/hooks";
|
|
20
16
|
import useNetworks from "@src/hooks/networkManagement/useNetworks";
|
|
21
17
|
import {
|
|
@@ -131,11 +127,6 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
131
127
|
"MessageSent(bytes32,uint64,address,bytes,address,uint256,uint256,address,uint256)"; // TODO think of getting this signature in a different way becase its easy to forgot about it when we will change event on smartcontract
|
|
132
128
|
const messageReceivedEventSignature =
|
|
133
129
|
"MessageReceived(bytes32,uint64,address,bytes,address,uint256)"; // TODO think of getting this signature in a different way because its easy to forgot about it when we will change event on smartcontract
|
|
134
|
-
// cross-chain bridge via custom xswap router
|
|
135
|
-
const messageSentXSwapCustomRouterEventSignature =
|
|
136
|
-
"MessageSent(bytes32,uint64,address,address,address,address,uint256)"; // TODO think of getting this signature in a different way becase its easy to forgot about it when we will change event on smartcontract
|
|
137
|
-
const messageReceivedXSwapCustomRouterEventSignature =
|
|
138
|
-
"MessageReceived(bytes32,uint64,address,bytes)"; // TODO think of getting this signature in a different way because its easy to forgot about it when we will change event on smartcontract
|
|
139
130
|
|
|
140
131
|
const destinationChainProvider = ethers.getDefaultProvider(
|
|
141
132
|
supportedChains.find(
|
|
@@ -146,7 +137,6 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
146
137
|
);
|
|
147
138
|
|
|
148
139
|
const handleMessageEvent = async (
|
|
149
|
-
contractKey: "XSwapRouter" | "CustomXSwapRouter",
|
|
150
140
|
messageSentEventSignature: string,
|
|
151
141
|
messageReceivedEventSignature: string,
|
|
152
142
|
) => {
|
|
@@ -159,15 +149,13 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
159
149
|
transactionData.messageId = messageId;
|
|
160
150
|
|
|
161
151
|
const xSwapRouterOnDestination = new ethers.Contract(
|
|
162
|
-
ADDRESSES[dstChain!.chainId]
|
|
163
|
-
|
|
164
|
-
? XSwapRouterAbi
|
|
165
|
-
: CustomXSwapRouterAbi,
|
|
152
|
+
ADDRESSES[dstChain!.chainId]!.XSwapRouter!,
|
|
153
|
+
XSwapRouterAbi,
|
|
166
154
|
destinationChainProvider,
|
|
167
155
|
);
|
|
168
156
|
|
|
169
157
|
const eventFilter = {
|
|
170
|
-
address: ADDRESSES[dstChain.chainId]?.
|
|
158
|
+
address: ADDRESSES[dstChain.chainId]?.XSwapRouter,
|
|
171
159
|
topics: [
|
|
172
160
|
ethers.utils.id(messageReceivedEventSignature),
|
|
173
161
|
messageId,
|
|
@@ -194,16 +182,9 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
194
182
|
};
|
|
195
183
|
|
|
196
184
|
await handleMessageEvent(
|
|
197
|
-
"XSwapRouter",
|
|
198
185
|
messageSentEventSignature,
|
|
199
186
|
messageReceivedEventSignature,
|
|
200
187
|
);
|
|
201
|
-
|
|
202
|
-
await handleMessageEvent(
|
|
203
|
-
"CustomXSwapRouter",
|
|
204
|
-
messageSentXSwapCustomRouterEventSignature,
|
|
205
|
-
messageReceivedXSwapCustomRouterEventSignature,
|
|
206
|
-
);
|
|
207
188
|
}
|
|
208
189
|
|
|
209
190
|
addTransactionToLocalHistory(transactionData, txReceipt.from);
|
|
@@ -241,7 +222,7 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
241
222
|
handlers: {
|
|
242
223
|
onSuccess: (data) => {
|
|
243
224
|
setTxStatus(TxStatus.COMPLETED);
|
|
244
|
-
refreshBalance();
|
|
225
|
+
refreshBalance({ forceRpc: true });
|
|
245
226
|
trackTransaction(data);
|
|
246
227
|
if (
|
|
247
228
|
srcChain?.chainId &&
|
|
@@ -419,7 +400,7 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
419
400
|
handlers: {
|
|
420
401
|
onSuccess: () => {
|
|
421
402
|
setTxStatus(TxStatus.COMPLETED);
|
|
422
|
-
refreshBalance();
|
|
403
|
+
refreshBalance({ forceRpc: true });
|
|
423
404
|
},
|
|
424
405
|
},
|
|
425
406
|
network: Ecosystem.SOLANA,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
SOLANA_TOKEN_2022_PROGRAM_ID,
|
|
9
9
|
SOLANA_TOKEN_PROGRAM_ID,
|
|
10
10
|
} from "@src/constants";
|
|
11
|
-
import { ADDRESSES } from "@src/contracts";
|
|
11
|
+
import { ADDRESSES, resolveBridgeDisplayToken } from "@src/contracts";
|
|
12
12
|
import {
|
|
13
13
|
BridgeToken,
|
|
14
14
|
BridgeTokensDictionary,
|
|
@@ -117,7 +117,7 @@ type SwapContext = {
|
|
|
117
117
|
isFetchingBalances: boolean;
|
|
118
118
|
isExpressDeliveryActive: boolean;
|
|
119
119
|
quoteRoute: () => Promise<void>;
|
|
120
|
-
refreshBalance: () => void;
|
|
120
|
+
refreshBalance: (options?: { forceRpc?: boolean }) => void;
|
|
121
121
|
findTokenDataByAddressAndChain: (
|
|
122
122
|
tokenAddress: string,
|
|
123
123
|
chainId: string,
|
|
@@ -279,6 +279,7 @@ export const SwapProvider = ({
|
|
|
279
279
|
|
|
280
280
|
const routeAbortController = useRef<AbortController | null>(null);
|
|
281
281
|
const refreshSelectedTokenBalanceNonce = useRef(crypto.randomUUID());
|
|
282
|
+
const prevWalletAddresses = useRef<string | undefined>(undefined);
|
|
282
283
|
|
|
283
284
|
// =============================================================================
|
|
284
285
|
// Memoized Values
|
|
@@ -383,8 +384,15 @@ export const SwapProvider = ({
|
|
|
383
384
|
// Skip invalid token pairs.
|
|
384
385
|
continue;
|
|
385
386
|
}
|
|
386
|
-
const [chainId0,
|
|
387
|
-
const [chainId1,
|
|
387
|
+
const [chainId0, rawAddress0] = entries[0]!;
|
|
388
|
+
const [chainId1, rawAddress1] = entries[1]!;
|
|
389
|
+
|
|
390
|
+
// Some pairs use an internal, CCIP-connected token (e.g. a wrapped
|
|
391
|
+
// variant) that the backend relies on for routing. In the bridge UI we
|
|
392
|
+
// display/select the user-facing token instead; the backend wraps it into
|
|
393
|
+
// the CCIP token when building the route.
|
|
394
|
+
const address0 = resolveBridgeDisplayToken(chainId0, rawAddress0);
|
|
395
|
+
const address1 = resolveBridgeDisplayToken(chainId1, rawAddress1);
|
|
388
396
|
|
|
389
397
|
addMapping(chainId0, address0, chainId1, address1);
|
|
390
398
|
addMapping(chainId1, address1, chainId0, address0);
|
|
@@ -661,77 +669,96 @@ export const SwapProvider = ({
|
|
|
661
669
|
srcValueWei,
|
|
662
670
|
]);
|
|
663
671
|
// refreshes and updates the balance of the source token using its RPC URL.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
refreshSelectedTokenBalanceNonce.current = nonce;
|
|
676
|
-
setIsFetchingBalance(true);
|
|
677
|
-
let balance = "0";
|
|
678
|
-
if (srcChain?.ecosystem === Ecosystem.EVM && evmAddress) {
|
|
679
|
-
const balanceFromBackend =
|
|
680
|
-
tokenBalances[srcChain?.chainId]?.[srcToken.address];
|
|
681
|
-
if (balanceFromBackend === undefined) {
|
|
682
|
-
const balanceUI = await getBalanceOf(
|
|
683
|
-
evmAddress,
|
|
684
|
-
srcToken.address,
|
|
685
|
-
srcChain?.publicRpcUrls[0],
|
|
686
|
-
);
|
|
687
|
-
balance = humanReadableToWei({
|
|
688
|
-
amount: balanceUI,
|
|
689
|
-
decimals: srcToken.decimals,
|
|
690
|
-
});
|
|
691
|
-
} else {
|
|
692
|
-
balance = balanceFromBackend;
|
|
672
|
+
// pass { forceRpc: true } to skip the backend balances cache (e.g. right
|
|
673
|
+
// after a transaction, when the cached value is already stale).
|
|
674
|
+
const refreshBalance = useCallback(
|
|
675
|
+
async (options?: { forceRpc?: boolean }) => {
|
|
676
|
+
if (
|
|
677
|
+
(!evmAddress && !solanaAddress) ||
|
|
678
|
+
!srcChain?.publicRpcUrls[0] ||
|
|
679
|
+
!srcToken ||
|
|
680
|
+
!srcToken?.address
|
|
681
|
+
) {
|
|
682
|
+
return;
|
|
693
683
|
}
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
684
|
+
|
|
685
|
+
const nonce = crypto.randomUUID();
|
|
686
|
+
refreshSelectedTokenBalanceNonce.current = nonce;
|
|
687
|
+
setIsFetchingBalance(true);
|
|
688
|
+
let balance = "0";
|
|
689
|
+
if (srcChain?.ecosystem === Ecosystem.EVM && evmAddress) {
|
|
690
|
+
const balanceFromBackend =
|
|
691
|
+
tokenBalances[srcChain?.chainId]?.[srcToken.address];
|
|
692
|
+
if (options?.forceRpc || balanceFromBackend === undefined) {
|
|
693
|
+
const balanceUI = await getBalanceOf(
|
|
694
|
+
evmAddress,
|
|
695
|
+
srcToken.address,
|
|
696
|
+
srcChain?.publicRpcUrls[0],
|
|
697
|
+
);
|
|
698
|
+
balance = humanReadableToWei({
|
|
699
|
+
amount: balanceUI,
|
|
700
|
+
decimals: srcToken.decimals,
|
|
711
701
|
});
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
702
|
+
if (options?.forceRpc && balanceFromBackend !== undefined) {
|
|
703
|
+
// keep the backend cache in sync so token pickers and future
|
|
704
|
+
// non-forced refreshes see the fresh value
|
|
705
|
+
const chainId = srcChain.chainId;
|
|
706
|
+
const tokenAddress = srcToken.address;
|
|
707
|
+
const freshBalance = balance;
|
|
708
|
+
setTokenBalances((prev) => ({
|
|
709
|
+
...prev,
|
|
710
|
+
[chainId]: {
|
|
711
|
+
...prev[chainId],
|
|
712
|
+
[tokenAddress]: freshBalance,
|
|
713
|
+
},
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
722
716
|
} else {
|
|
723
|
-
balance =
|
|
717
|
+
balance = balanceFromBackend;
|
|
718
|
+
}
|
|
719
|
+
} else if (srcChain?.ecosystem === Ecosystem.SOLANA && solanaAddress) {
|
|
720
|
+
const solanaPublicKey = new PublicKey(solanaAddress);
|
|
721
|
+
const connection = new Connection(srcChain.publicRpcUrls[0]!);
|
|
722
|
+
if (srcToken.address === SOLANA_NATIVE_TOKEN_ADDRESS.toBase58()) {
|
|
723
|
+
balance = new BigNumberJS(
|
|
724
|
+
await connection.getBalance(solanaPublicKey),
|
|
725
|
+
).toString();
|
|
726
|
+
} else {
|
|
727
|
+
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
728
|
+
solanaPublicKey,
|
|
729
|
+
{
|
|
730
|
+
programId: SOLANA_TOKEN_PROGRAM_ID,
|
|
731
|
+
},
|
|
732
|
+
);
|
|
733
|
+
const token2022Accounts =
|
|
734
|
+
await connection.getParsedTokenAccountsByOwner(solanaPublicKey, {
|
|
735
|
+
programId: SOLANA_TOKEN_2022_PROGRAM_ID,
|
|
736
|
+
});
|
|
737
|
+
const allTokenAccount = [
|
|
738
|
+
...tokenAccounts.value,
|
|
739
|
+
...token2022Accounts.value,
|
|
740
|
+
];
|
|
741
|
+
const tokenAccount = allTokenAccount.find(
|
|
742
|
+
(account) =>
|
|
743
|
+
account.account.data.parsed.info.mint === srcToken.address,
|
|
744
|
+
);
|
|
745
|
+
if (!tokenAccount) {
|
|
746
|
+
balance = "0";
|
|
747
|
+
} else {
|
|
748
|
+
balance = tokenAccount.account.data.parsed.info.tokenAmount.amount;
|
|
749
|
+
}
|
|
724
750
|
}
|
|
725
751
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
752
|
+
if (nonce !== refreshSelectedTokenBalanceNonce.current) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
730
755
|
|
|
731
|
-
|
|
756
|
+
setSrcTokenBalanceWei(balance);
|
|
732
757
|
|
|
733
|
-
|
|
734
|
-
|
|
758
|
+
setIsFetchingBalance(false);
|
|
759
|
+
},
|
|
760
|
+
[evmAddress, solanaAddress, srcChain, srcToken, tokenBalances],
|
|
761
|
+
);
|
|
735
762
|
// retrieves token details for a given token address and chain from supported tokens mapping. (including imported tokens)
|
|
736
763
|
const findTokenDataByAddressAndChain = useCallback(
|
|
737
764
|
(tokenAddress: string, chainId: string): Token | undefined => {
|
|
@@ -794,9 +821,16 @@ export const SwapProvider = ({
|
|
|
794
821
|
quoteRoute();
|
|
795
822
|
}, [quoteRoute]);
|
|
796
823
|
// calls refreshBalance to update source token balance on mount or when refreshBalance changes.
|
|
824
|
+
// when the connected wallet changed, force an RPC read — the cached backend
|
|
825
|
+
// balances still belong to the previous wallet until getBalances resolves.
|
|
797
826
|
useEffect(() => {
|
|
798
|
-
|
|
799
|
-
|
|
827
|
+
const walletAddresses = `${evmAddress ?? ""}:${solanaAddress ?? ""}`;
|
|
828
|
+
const walletChanged =
|
|
829
|
+
prevWalletAddresses.current !== undefined &&
|
|
830
|
+
prevWalletAddresses.current !== walletAddresses;
|
|
831
|
+
prevWalletAddresses.current = walletAddresses;
|
|
832
|
+
refreshBalance(walletChanged ? { forceRpc: true } : undefined);
|
|
833
|
+
}, [refreshBalance, evmAddress, solanaAddress]);
|
|
800
834
|
// fetches user all token balances when a wallet address is available and supported chains are loaded.
|
|
801
835
|
useEffect(() => {
|
|
802
836
|
let isCancelled = false;
|
|
@@ -2,12 +2,10 @@ import BatchQuery from "./BatchQuery.json";
|
|
|
2
2
|
import ERC20 from "./ERC20.json";
|
|
3
3
|
import XSwapRouter from "./XSwapRouter.json";
|
|
4
4
|
import XSwapTokenFeeDistributor from "./XSwapTokenFeeDistributor.json";
|
|
5
|
-
import CustomXSwapRouter from "./CustomXSwapRouter.json";
|
|
6
5
|
import StakingStableAPR from "./StakingStableAPR.json";
|
|
7
6
|
|
|
8
7
|
export const BatchQueryAbi = BatchQuery;
|
|
9
8
|
export const ERC20Abi = ERC20;
|
|
10
9
|
export const XSwapRouterAbi = XSwapRouter;
|
|
11
10
|
export const XSwapTokenFeeDistributorAbi = XSwapTokenFeeDistributor;
|
|
12
|
-
export const CustomXSwapRouterAbi = CustomXSwapRouter;
|
|
13
11
|
export const StakingStableAPRAbi = StakingStableAPR;
|
|
@@ -23,8 +23,6 @@ export const ADDRESSES: Addresses = {
|
|
|
23
23
|
"8453": {
|
|
24
24
|
[ContractName.USDC]: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
25
25
|
[ContractName.BatchQuery]: "0xb5f9934ea0810ef5faa677e8c68c6983b88664b1",
|
|
26
|
-
[ContractName.CustomXSwapRouter]:
|
|
27
|
-
"0x9B64aC0aD647768838a3957Bc12c40E1C539cb93",
|
|
28
26
|
[ContractName.FeeCollector]: "0xb37275558f02f05104c2ba35199d16adeb43432f",
|
|
29
27
|
[ContractName.StakingCosmicCadets]:
|
|
30
28
|
"0x67798736bFb7d9A1F143215E258fe15Da68c85F0",
|
|
@@ -41,8 +39,6 @@ export const ADDRESSES: Addresses = {
|
|
|
41
39
|
"1": {
|
|
42
40
|
[ContractName.USDC]: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
43
41
|
[ContractName.BatchQuery]: "0xb5f9934ea0810ef5faa677e8c68c6983b88664b1",
|
|
44
|
-
[ContractName.CustomXSwapRouter]:
|
|
45
|
-
"0x9B64aC0aD647768838a3957Bc12c40E1C539cb93",
|
|
46
42
|
[ContractName.FeeCollector]: "0xb37275558f02f05104c2ba35199d16adeb43432f",
|
|
47
43
|
[ContractName.XSwapRouter]: "0xe1c14b9f065dead2e89ee35382f8bd42bdb87a04",
|
|
48
44
|
[ContractName.XSwapToken]: "0x8fe815417913a93ea99049fc0718ee1647a2a07c",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge UI display overrides.
|
|
3
|
+
*
|
|
4
|
+
* Some tokens returned by the `/bridgeTokens` endpoint are internal,
|
|
5
|
+
* directly-CCIP-connected tokens (e.g. a wrapped variant) that the backend
|
|
6
|
+
* relies on for routing, but that we do NOT want users to see or pick in the
|
|
7
|
+
* bridge UI. This map replaces such a CCIP token with the user-facing token we
|
|
8
|
+
* display and let users select / hold instead.
|
|
9
|
+
*
|
|
10
|
+
* The backend accepts the user-facing token in `/route` and wraps it into the
|
|
11
|
+
* CCIP token under the hood (e.g. real USDC -> XSwapUSDC via the wrapper), so
|
|
12
|
+
* the selected token is what flows through balances, approval and the route
|
|
13
|
+
* request, while the actual cross-chain hop still uses the CCIP token.
|
|
14
|
+
*
|
|
15
|
+
* Shape: { [chainId]: { [ccipTokenAddressLowercased]: displayTokenAddressLowercased } }
|
|
16
|
+
*
|
|
17
|
+
* IMPORTANT: addresses must be lowercased on both sides — the bridge dictionary
|
|
18
|
+
* and its consumers compare EVM addresses lowercased.
|
|
19
|
+
*/
|
|
20
|
+
export const bridgeDisplayTokenOverrides: Record<
|
|
21
|
+
string,
|
|
22
|
+
Record<string, string>
|
|
23
|
+
> = {
|
|
24
|
+
// base: XSwapUSDC -> real USDC
|
|
25
|
+
"8453": {
|
|
26
|
+
"0x5426188a207fd0365dca5cff345059ff531d1aa7":
|
|
27
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the user-facing token address to display in the bridge UI for a given
|
|
33
|
+
* `/bridgeTokens` entry, or the original address when there is no override.
|
|
34
|
+
*
|
|
35
|
+
* EVM addresses are matched case-insensitively; non-EVM addresses (e.g. Solana)
|
|
36
|
+
* are returned untouched.
|
|
37
|
+
*/
|
|
38
|
+
export const resolveBridgeDisplayToken = (
|
|
39
|
+
chainId: string,
|
|
40
|
+
tokenAddress: string,
|
|
41
|
+
): string => {
|
|
42
|
+
return (
|
|
43
|
+
bridgeDisplayTokenOverrides[chainId]?.[tokenAddress.toLowerCase()] ??
|
|
44
|
+
tokenAddress
|
|
45
|
+
);
|
|
46
|
+
};
|
package/src/contracts/index.ts
CHANGED
package/src/models/Addresses.ts
CHANGED
|
@@ -9,7 +9,6 @@ export type Contracts = Partial<{
|
|
|
9
9
|
export enum ContractName {
|
|
10
10
|
USDC = "USDC",
|
|
11
11
|
BatchQuery = "BatchQuery",
|
|
12
|
-
CustomXSwapRouter = "CustomXSwapRouter",
|
|
13
12
|
FeeCollector = "FeeCollector",
|
|
14
13
|
StakingCosmicCadets = "StakingCosmicCadets",
|
|
15
14
|
StakingSolarDivision = "StakingSolarDivision",
|