diff --git a/.env.example b/.env.example
index 04a695e86..59830e7ce 100644
--- a/.env.example
+++ b/.env.example
@@ -9,12 +9,14 @@ RESEARCH_ORIGIN=https://research.lido.fi
EL_RPC_URLS_1=
EL_RPC_URLS_17000=
EL_RPC_URLS_11155111=
+EL_RPC_URLS_10=
EL_RPC_URLS_11155420=
# IPFS prefill RPC URLs - list of URLs delimited by commas
PREFILL_UNSAFE_EL_RPC_URLS_1=
PREFILL_UNSAFE_EL_RPC_URLS_17000=
PREFILL_UNSAFE_EL_RPC_URLS_11155111=
+PREFILL_UNSAFE_EL_RPC_URLS_10=
PREFILL_UNSAFE_EL_RPC_URLS_11155420=
# supported networks for connecting wallet
diff --git a/IPFS.json b/IPFS.json
index 3e967c8b0..7c62eb623 100644
--- a/IPFS.json
+++ b/IPFS.json
@@ -31,14 +31,5 @@
"enabledWithdrawalDexes": ["one-inch", "paraswap", "bebop"],
"multiChainBanner": []
}
- },
- "11155420": {
- "__warning__": "For testing purposes only",
- "cid": "",
- "leastSafeVersion": "0.36.1",
- "config": {
- "enabledWithdrawalDexes": ["one-inch", "paraswap", "bebop"],
- "multiChainBanner": []
- }
}
}
diff --git a/abi/l2-steth.abi.json b/abi/l2-steth.abi.json
new file mode 100644
index 000000000..247cdc8f4
--- /dev/null
+++ b/abi/l2-steth.abi.json
@@ -0,0 +1,438 @@
+[
+ {
+ "inputs": [
+ { "internalType": "string", "name": "name_", "type": "string" },
+ { "internalType": "string", "name": "symbol_", "type": "string" },
+ { "internalType": "string", "name": "version_", "type": "string" },
+ { "internalType": "uint8", "name": "decimals_", "type": "uint8" },
+ {
+ "internalType": "address",
+ "name": "tokenToWrapFrom_",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "tokenRateOracle_",
+ "type": "address"
+ },
+ { "internalType": "address", "name": "bridge_", "type": "address" }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ { "inputs": [], "name": "ErrorAccountIsZeroAddress", "type": "error" },
+ { "inputs": [], "name": "ErrorDeadlineExpired", "type": "error" },
+ { "inputs": [], "name": "ErrorInvalidSignature", "type": "error" },
+ { "inputs": [], "name": "ErrorNameIsEmpty", "type": "error" },
+ { "inputs": [], "name": "ErrorNotBridge", "type": "error" },
+ { "inputs": [], "name": "ErrorNotEnoughAllowance", "type": "error" },
+ { "inputs": [], "name": "ErrorNotEnoughBalance", "type": "error" },
+ { "inputs": [], "name": "ErrorSymbolIsEmpty", "type": "error" },
+ { "inputs": [], "name": "ErrorTransferToRebasableContract", "type": "error" },
+ {
+ "inputs": [],
+ "name": "ErrorZeroAddressL2ERC20TokenBridge",
+ "type": "error"
+ },
+ { "inputs": [], "name": "ErrorZeroAddressTokenRateOracle", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroAddressTokenToWrapFrom", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroDecimals", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroSharesUnwrap", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroSharesWrap", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroTokensUnwrap", "type": "error" },
+ { "inputs": [], "name": "InvalidContractVersionIncrement", "type": "error" },
+ { "inputs": [], "name": "NonZeroContractVersionOnInit", "type": "error" },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "expected", "type": "uint256" },
+ { "internalType": "uint256", "name": "received", "type": "uint256" }
+ ],
+ "name": "UnexpectedContractVersion",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "version",
+ "type": "uint256"
+ }
+ ],
+ "name": "ContractVersionSet",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "sharesValue",
+ "type": "uint256"
+ }
+ ],
+ "name": "TransferShares",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "DOMAIN_SEPARATOR",
+ "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "L2_ERC20_TOKEN_BRIDGE",
+ "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "TOKEN_RATE_ORACLE",
+ "outputs": [
+ {
+ "internalType": "contract ITokenRateOracle",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "TOKEN_RATE_ORACLE_DECIMALS",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "TOKEN_TO_WRAP_FROM",
+ "outputs": [
+ { "internalType": "contract IERC20", "name": "", "type": "address" }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" },
+ { "internalType": "address", "name": "spender", "type": "address" }
+ ],
+ "name": "allowance",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "spender_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "approve",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "account_", "type": "address" }
+ ],
+ "name": "balanceOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "account_", "type": "address" },
+ { "internalType": "uint256", "name": "tokenAmount_", "type": "uint256" }
+ ],
+ "name": "bridgeUnwrap",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "account_", "type": "address" },
+ { "internalType": "uint256", "name": "sharesAmount_", "type": "uint256" }
+ ],
+ "name": "bridgeWrap",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "eip712Domain",
+ "outputs": [
+ { "internalType": "bytes1", "name": "fields", "type": "bytes1" },
+ { "internalType": "string", "name": "name", "type": "string" },
+ { "internalType": "string", "name": "version", "type": "string" },
+ { "internalType": "uint256", "name": "chainId", "type": "uint256" },
+ {
+ "internalType": "address",
+ "name": "verifyingContract",
+ "type": "address"
+ },
+ { "internalType": "bytes32", "name": "salt", "type": "bytes32" },
+ { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getContractVersion",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "tokenAmount_", "type": "uint256" }
+ ],
+ "name": "getSharesByTokens",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "sharesAmount_", "type": "uint256" }
+ ],
+ "name": "getTokensByShares",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getTotalShares",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "string", "name": "name_", "type": "string" },
+ { "internalType": "string", "name": "symbol_", "type": "string" },
+ { "internalType": "string", "name": "version_", "type": "string" }
+ ],
+ "name": "initialize",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" }
+ ],
+ "name": "nonces",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner_", "type": "address" },
+ { "internalType": "address", "name": "spender_", "type": "address" },
+ { "internalType": "uint256", "name": "value_", "type": "uint256" },
+ { "internalType": "uint256", "name": "deadline_", "type": "uint256" },
+ { "internalType": "bytes", "name": "signature_", "type": "bytes" }
+ ],
+ "name": "permit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner_", "type": "address" },
+ { "internalType": "address", "name": "spender_", "type": "address" },
+ { "internalType": "uint256", "name": "value_", "type": "uint256" },
+ { "internalType": "uint256", "name": "deadline_", "type": "uint256" },
+ { "internalType": "uint8", "name": "v_", "type": "uint8" },
+ { "internalType": "bytes32", "name": "r_", "type": "bytes32" },
+ { "internalType": "bytes32", "name": "s_", "type": "bytes32" }
+ ],
+ "name": "permit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "account_", "type": "address" }
+ ],
+ "name": "sharesOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "transfer",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "from_", "type": "address" },
+ { "internalType": "address", "name": "to_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "transferFrom",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "recipient_", "type": "address" },
+ { "internalType": "uint256", "name": "sharesAmount_", "type": "uint256" }
+ ],
+ "name": "transferShares",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "sender_", "type": "address" },
+ { "internalType": "address", "name": "recipient_", "type": "address" },
+ { "internalType": "uint256", "name": "sharesAmount_", "type": "uint256" }
+ ],
+ "name": "transferSharesFrom",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "tokenAmount_", "type": "uint256" }
+ ],
+ "name": "unwrap",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "sharesAmount_", "type": "uint256" }
+ ],
+ "name": "unwrapShares",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "sharesAmount_", "type": "uint256" }
+ ],
+ "name": "wrap",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
diff --git a/abi/l2-wstesth.abi.json b/abi/l2-wstesth.abi.json
new file mode 100644
index 000000000..518cc00c8
--- /dev/null
+++ b/abi/l2-wstesth.abi.json
@@ -0,0 +1,296 @@
+[
+ {
+ "inputs": [
+ { "internalType": "string", "name": "name_", "type": "string" },
+ { "internalType": "string", "name": "symbol_", "type": "string" },
+ { "internalType": "string", "name": "version_", "type": "string" },
+ { "internalType": "uint8", "name": "decimals_", "type": "uint8" },
+ { "internalType": "address", "name": "bridge_", "type": "address" }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ { "inputs": [], "name": "ErrorAccountIsZeroAddress", "type": "error" },
+ { "inputs": [], "name": "ErrorDeadlineExpired", "type": "error" },
+ { "inputs": [], "name": "ErrorInvalidSignature", "type": "error" },
+ {
+ "inputs": [],
+ "name": "ErrorMetadataIsAlreadyInitialized",
+ "type": "error"
+ },
+ { "inputs": [], "name": "ErrorMetadataIsNotInitialized", "type": "error" },
+ { "inputs": [], "name": "ErrorNameIsEmpty", "type": "error" },
+ { "inputs": [], "name": "ErrorNotBridge", "type": "error" },
+ { "inputs": [], "name": "ErrorNotEnoughAllowance", "type": "error" },
+ { "inputs": [], "name": "ErrorNotEnoughBalance", "type": "error" },
+ { "inputs": [], "name": "ErrorSymbolIsEmpty", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroAddressBridge", "type": "error" },
+ { "inputs": [], "name": "ErrorZeroDecimals", "type": "error" },
+ { "inputs": [], "name": "InvalidContractVersionIncrement", "type": "error" },
+ { "inputs": [], "name": "NonZeroContractVersionOnInit", "type": "error" },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "expected", "type": "uint256" },
+ { "internalType": "uint256", "name": "received", "type": "uint256" }
+ ],
+ "name": "UnexpectedContractVersion",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "version",
+ "type": "uint256"
+ }
+ ],
+ "name": "ContractVersionSet",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "DOMAIN_SEPARATOR",
+ "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "", "type": "address" },
+ { "internalType": "address", "name": "", "type": "address" }
+ ],
+ "name": "allowance",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "spender_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "approve",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "name": "balanceOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "bridge",
+ "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "account_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "bridgeBurn",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "account_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "bridgeMint",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "eip712Domain",
+ "outputs": [
+ { "internalType": "bytes1", "name": "fields", "type": "bytes1" },
+ { "internalType": "string", "name": "name", "type": "string" },
+ { "internalType": "string", "name": "version", "type": "string" },
+ { "internalType": "uint256", "name": "chainId", "type": "uint256" },
+ {
+ "internalType": "address",
+ "name": "verifyingContract",
+ "type": "address"
+ },
+ { "internalType": "bytes32", "name": "salt", "type": "bytes32" },
+ { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "string", "name": "name_", "type": "string" },
+ { "internalType": "string", "name": "version_", "type": "string" }
+ ],
+ "name": "finalizeUpgrade_v2",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getContractVersion",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "string", "name": "name_", "type": "string" },
+ { "internalType": "string", "name": "symbol_", "type": "string" },
+ { "internalType": "string", "name": "version_", "type": "string" }
+ ],
+ "name": "initialize",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" }
+ ],
+ "name": "nonces",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner_", "type": "address" },
+ { "internalType": "address", "name": "spender_", "type": "address" },
+ { "internalType": "uint256", "name": "value_", "type": "uint256" },
+ { "internalType": "uint256", "name": "deadline_", "type": "uint256" },
+ { "internalType": "bytes", "name": "signature_", "type": "bytes" }
+ ],
+ "name": "permit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner_", "type": "address" },
+ { "internalType": "address", "name": "spender_", "type": "address" },
+ { "internalType": "uint256", "name": "value_", "type": "uint256" },
+ { "internalType": "uint256", "name": "deadline_", "type": "uint256" },
+ { "internalType": "uint8", "name": "v_", "type": "uint8" },
+ { "internalType": "bytes32", "name": "r_", "type": "bytes32" },
+ { "internalType": "bytes32", "name": "s_", "type": "bytes32" }
+ ],
+ "name": "permit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "transfer",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "from_", "type": "address" },
+ { "internalType": "address", "name": "to_", "type": "address" },
+ { "internalType": "uint256", "name": "amount_", "type": "uint256" }
+ ],
+ "name": "transferFrom",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
diff --git a/assets/icons/chain-toggler/mainnet.svg b/assets/icons/chain-toggler/mainnet.svg
new file mode 100644
index 000000000..892644b07
--- /dev/null
+++ b/assets/icons/chain-toggler/mainnet.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/assets/icons/chain-toggler/optimism.svg b/assets/icons/chain-toggler/optimism.svg
new file mode 100644
index 000000000..7bb1b5676
--- /dev/null
+++ b/assets/icons/chain-toggler/optimism.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/config/get-secret-config.ts b/config/get-secret-config.ts
index d992aacc9..4c67c6103 100644
--- a/config/get-secret-config.ts
+++ b/config/get-secret-config.ts
@@ -11,6 +11,8 @@ export type SecretConfigType = Modify<
rpcUrls_1: [string, ...string[]];
rpcUrls_17000: [string, ...string[]];
rpcUrls_11155111: [string, ...string[]];
+
+ rpcUrls_10: [string, ...string[]];
rpcUrls_11155420: [string, ...string[]];
// Dynamic keys like rpcUrls_
[key: `rpcUrls_${number}`]: string[];
@@ -45,6 +47,11 @@ export const getSecretConfig = (): SecretConfigType => {
],
rpcUrls_11155111: (serverRuntimeConfig.rpcUrls_11155111?.split(',') ??
[]) as [string, ...string[]],
+
+ rpcUrls_10: (serverRuntimeConfig.rpcUrls_10?.split(',') ?? []) as [
+ string,
+ ...string[],
+ ],
rpcUrls_11155420: (serverRuntimeConfig.rpcUrls_11155420?.split(',') ??
[]) as [string, ...string[]],
diff --git a/config/groups/web3.ts b/config/groups/web3.ts
index 85f00a9c3..57f3bdf32 100644
--- a/config/groups/web3.ts
+++ b/config/groups/web3.ts
@@ -8,8 +8,17 @@ export const PROVIDER_BATCH_TIME = 150;
export const PROVIDER_MAX_BATCH = 20;
// account for gas estimation
-// will always have >=0.001 ether, >=0.001 stETH, >=0.001 wstETH
-// on Mainnet, Holesky
+// will always have:
+// Balances:
+// >=0.001 ether(or native token), >=0.001 stETH, >=0.001 wstETH,
+// Contract States:
+// >=0.001 stETH allowance to wstETH (L1)
+// >=0.001 wsTETH allowance to WQ (L1)
+// >=0.001 wsTETH allowance to WQ (L1)
+// >=0.001 wsTETH allowance to stETH (L2)
+// >=0.001 wsTETH allowance to stETH (L2)
+
+// on Mainnet, Holesky, Sepolia, Optimism, Optimism Sepolia
export const ESTIMATE_ACCOUNT = '0x87c0e047F4e4D3e289A56a36570D4CB957A37Ef1';
export const ESTIMATE_AMOUNT = parseEther('0.001');
diff --git a/config/user-config/types.ts b/config/user-config/types.ts
index c16008fb9..e03070156 100644
--- a/config/user-config/types.ts
+++ b/config/user-config/types.ts
@@ -8,6 +8,7 @@ export type UserConfigDefaultType = {
[CHAINS.Holesky]: string[];
[CHAINS.Sepolia]: string[];
[CHAINS.OptimismSepolia]: string[];
+ [CHAINS.Optimism]: string[];
};
walletconnectProjectId: string | undefined;
};
diff --git a/config/user-config/utils.ts b/config/user-config/utils.ts
index 8e1acdd46..937466246 100644
--- a/config/user-config/utils.ts
+++ b/config/user-config/utils.ts
@@ -16,6 +16,7 @@ export const getUserConfigDefault = (): UserConfigDefaultType => {
[CHAINS.Mainnet]: config.prefillUnsafeElRpcUrls1,
[CHAINS.Holesky]: config.prefillUnsafeElRpcUrls17000,
[CHAINS.Sepolia]: config.prefillUnsafeElRpcUrls11155111,
+ [CHAINS.Optimism]: config.prefillUnsafeElRpcUrls10,
[CHAINS.OptimismSepolia]: config.prefillUnsafeElRpcUrls11155420,
},
walletconnectProjectId: config.walletconnectProjectId,
diff --git a/consts/chains.ts b/consts/chains.ts
index 223641982..1ed6c7c5e 100644
--- a/consts/chains.ts
+++ b/consts/chains.ts
@@ -1,7 +1,10 @@
+import { LIDO_L2_CONTRACT_ADDRESSES } from '@lidofinance/lido-ethereum-sdk/common';
+
export enum CHAINS {
Mainnet = 1,
Holesky = 17000,
Sepolia = 11155111,
+ Optimism = 10,
OptimismSepolia = 11155420,
}
@@ -25,10 +28,7 @@ export const SDK_LEGACY_SUPPORTED_CHAINS = [
CHAINS.Sepolia,
];
-// TODO: move to @lidofinance/lido-ethereum-sdk package
-export const SDK_SUPPORTED_MULTICHAIN_CHAINS = [CHAINS.OptimismSepolia];
-
// TODO: move to @lidofinance/lido-ethereum-sdk package
export const isSDKSupportedL2Chain = (chainId: CHAINS) => {
- return SDK_SUPPORTED_MULTICHAIN_CHAINS.indexOf(chainId) > -1;
+ return !!LIDO_L2_CONTRACT_ADDRESSES[chainId];
};
diff --git a/consts/matomo-click-events.ts b/consts/matomo-click-events.ts
index 91f34032c..d8042113c 100644
--- a/consts/matomo-click-events.ts
+++ b/consts/matomo-click-events.ts
@@ -35,10 +35,17 @@ export const enum MATOMO_CLICK_EVENTS_TYPES {
faqHowCanIUnstakeStEthIntegrations = 'faqHowCanIUnstakeStEthIntegrations',
faqHowCanIGetWstethWrapLink = 'faqHowCanIGetWstethWrapLink',
faqHowCanIGetWstethIntegrationsLink = 'faqHowCanIGetWstethIntegrationsLink',
+ faqHowCanIGetWstethOnOptimismWrapLink = 'faqHowCanIGetWstethOnOptimismWrapLink',
+ faqHowCanIGetWstethOnOptimismBridgeYourWstETHFromEthereumToOptimism = 'faqHowCanIGetWstethOnOptimismBridgeYourWstETHFromEthereumToOptimism',
+ faqHowCanIGetWstethOnOptimismIntegrations = 'faqHowCanIGetWstethOnOptimismIntegrations',
faqHowDoIUnwrapWstethUnwrapLink = 'faqHowDoIUnwrapWstethUnwrapLink',
faqHowCanIUseWstethLidoMultichain = 'faqHowCanIUseWstethLidoMultichain',
faqHowCanIUseWstethDefiProtocols = 'faqHowCanIUseWstethDefiProtocols',
+ faqHowCanIUseWstethOnOptimismDefiProtocols = 'faqHowCanIUseWstethOnOptimismDefiProtocols',
faqDoINeedToUnwrapMyWstethWithdrawalsTabs = 'faqDoINeedToUnwrapMyWstethWithdrawalsTabs',
+ faqHowCouldIUnwrapWstETHBackToStETHOnOptimismUnwrapLink = 'faqHowCouldIUnwrapWstETHBackToStETHOnOptimismUnwrapLink',
+ faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismBridgeYourWstETHOrStETHBack = 'faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismBridgeYourWstETHOrStETHBack',
+ faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismWithdrawalsRequestAndClaim = 'faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismWithdrawalsRequestAndClaim',
// /wrap page
wrapTokenSelectSTETH = 'wrapTokenSelectSteth',
wrapTokenSelectETH = 'wrapTokenSelectEth',
@@ -237,6 +244,22 @@ export const MATOMO_CLICK_EVENTS: Record<
'Push «DEX Lido integrations» in FAQ How can I get wstETH',
'eth_widget_faq_howgetwsteth_dexLidoIntegrations',
],
+ [MATOMO_CLICK_EVENTS_TYPES.faqHowCanIGetWstethOnOptimismWrapLink]: [
+ 'Ethereum_Staking_Widget',
+ 'Push «Wrap & Unwrap staking widget» in FAQ How can I get wstETH (Optimism)',
+ 'eth_widget_faq_howgetwsteth_wrap_optimism',
+ ],
+ [MATOMO_CLICK_EVENTS_TYPES.faqHowCanIGetWstethOnOptimismBridgeYourWstETHFromEthereumToOptimism]:
+ [
+ 'Ethereum_Staking_Widget',
+ 'Push «bridge your wstETH from Ethereum to Optimism» in How can I get wstETH on Optimism?',
+ 'eth_widget_faq_howCanIGetWstethOnOptimism_bridgeYourWstETHFromEthereumToOptimism',
+ ],
+ [MATOMO_CLICK_EVENTS_TYPES.faqHowCanIGetWstethOnOptimismIntegrations]: [
+ 'Ethereum_Staking_Widget',
+ 'Push «DEX Lido integrations» in FAQ How can I get wstETH (Optimism)',
+ 'eth_widget_faq_howgetwsteth_dexLidoIntegrations_optimism',
+ ],
[MATOMO_CLICK_EVENTS_TYPES.faqHowDoIUnwrapWstethUnwrapLink]: [
'Ethereum_Staking_Widget',
'Push «stake.lido.fi/wrap/unwrap» How do I unwrap wstETH back to stETH?',
@@ -247,6 +270,11 @@ export const MATOMO_CLICK_EVENTS: Record<
'Push «L2» How can I use wstETH?', // L2 naiming for analytics history consistency
'eth_widget_faq_howCanIUseWstETH_l2', // L2 naiming for analytics history consistency
],
+ [MATOMO_CLICK_EVENTS_TYPES.faqHowCanIUseWstethOnOptimismDefiProtocols]: [
+ 'Ethereum_Staking_Widget',
+ 'Push «L2» How can I use wstETH? (Optimism)',
+ 'eth_widget_faq_howCanIUseWstETH_l2_optimism',
+ ],
[MATOMO_CLICK_EVENTS_TYPES.faqHowCanIUseWstethDefiProtocols]: [
'Ethereum_Staking_Widget',
'Push «DeFi protocols» How can I use wstETH?',
@@ -257,6 +285,24 @@ export const MATOMO_CLICK_EVENTS: Record<
'Push «Withdrawals Request and Claim tabs» Do I need to unwrap my wstETH before requesting withdrawals?',
'eth_widget_faq_doINeedToUnwrapMyWsteth_withdrawalsRequestAndClaimTabs',
],
+ [MATOMO_CLICK_EVENTS_TYPES.faqHowCouldIUnwrapWstETHBackToStETHOnOptimismUnwrapLink]:
+ [
+ 'Ethereum_Staking_Widget',
+ 'Push «Wrap & Unwrap staking widget» How could I unwrap wstETH back to stETH on Optimism?',
+ 'eth_widget_faq_howCouldIUnwrapWstETHBackToStETHOnOptimismUnwrapLink',
+ ],
+ [MATOMO_CLICK_EVENTS_TYPES.faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismBridgeYourWstETHOrStETHBack]:
+ [
+ 'Ethereum_Staking_Widget',
+ 'Push «bridge your wstETH or stETH back» What happens if I want to unstake ETH on Ethereum? Can I do that from Optimism?',
+ 'eth_widget_faq_WhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismBridgeYourWstETHOrStETHBack',
+ ],
+ [MATOMO_CLICK_EVENTS_TYPES.faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismWithdrawalsRequestAndClaim]:
+ [
+ 'Ethereum_Staking_Widget',
+ 'Push «Withdrawals Request and Claim» What happens if I want to unstake ETH on Ethereum? Can I do that from Optimism?',
+ 'eth_widget_faq_WhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismWithdrawalsRequestAndClaim',
+ ],
// /wrap page
[MATOMO_CLICK_EVENTS_TYPES.wrapTokenSelectETH]: [
'Ethereum_Staking_Widget',
diff --git a/consts/tx.ts b/consts/tx.ts
index a2dd7c3dc..d011e0e6f 100644
--- a/consts/tx.ts
+++ b/consts/tx.ts
@@ -1,7 +1,11 @@
import { BigNumber } from 'ethers';
export const WSTETH_APPROVE_GAS_LIMIT = BigNumber.from(78000);
+export const STETH_L2_APPROVE_GAS_LIMIT = BigNumber.from(52500);
export const WRAP_FROM_ETH_GAS_LIMIT = BigNumber.from(100000);
export const WRAP_GAS_LIMIT = BigNumber.from(140000);
+export const WRAP_L2_GAS_LIMIT = BigNumber.from(95500);
+
export const UNWRAP_GAS_LIMIT = BigNumber.from(115000);
+export const UNWRAP_L2_GAS_LIMIT = BigNumber.from(77500);
diff --git a/env-dynamics.mjs b/env-dynamics.mjs
index 3c273329d..bb05b97a6 100644
--- a/env-dynamics.mjs
+++ b/env-dynamics.mjs
@@ -53,6 +53,8 @@ export const prefillUnsafeElRpcUrls17000 = process.env.PREFILL_UNSAFE_EL_RPC_URL
/** @type string[] */
export const prefillUnsafeElRpcUrls11155111 = process.env.PREFILL_UNSAFE_EL_RPC_URLS_11155111?.split(',') ?? [];
/** @type string[] */
+export const prefillUnsafeElRpcUrls10 = process.env.PREFILL_UNSAFE_EL_RPC_URLS_10?.split(',') ?? [];
+/** @type string[] */
export const prefillUnsafeElRpcUrls11155420 = process.env.PREFILL_UNSAFE_EL_RPC_URLS_11155420?.split(',') ?? [];
/** @type boolean */
diff --git a/features/rewards/components/IndexerLink.tsx b/features/rewards/components/IndexerLink.tsx
index 2f05fd919..b2a064ab7 100644
--- a/features/rewards/components/IndexerLink.tsx
+++ b/features/rewards/components/IndexerLink.tsx
@@ -1,5 +1,5 @@
import { Box, External as ExternalLinkIcon } from '@lidofinance/lido-ui';
-import { getEtherscanTxLink } from '@lido-sdk/helpers';
+import { getEtherscanTxLink } from 'utils/get-etherscan-tx-link';
import { config } from 'config';
diff --git a/features/rewards/components/rewardsListContent/RewardsListContent.tsx b/features/rewards/components/rewardsListContent/RewardsListContent.tsx
index b7c8ad636..db6be73b6 100644
--- a/features/rewards/components/rewardsListContent/RewardsListContent.tsx
+++ b/features/rewards/components/rewardsListContent/RewardsListContent.tsx
@@ -20,7 +20,8 @@ import {
import type { Address } from 'viem';
export const RewardsListContent: FC = () => {
- const { isWalletConnected, isSupportedChain } = useDappStatus();
+ const { isWalletConnected, isSupportedChain, isAccountActiveOnL2 } =
+ useDappStatus();
const {
address,
error,
@@ -38,7 +39,7 @@ export const RewardsListContent: FC = () => {
});
const hasSteth = stethBalance?.gt(Zero);
- if (isWalletConnected && !isSupportedChain)
+ if ((isWalletConnected && !isSupportedChain) || isAccountActiveOnL2)
return ;
if (!data && !initialLoading && !error) return ;
diff --git a/features/rewards/components/rewardsListContent/RewardsListsUnsupportedChain.tsx b/features/rewards/components/rewardsListContent/RewardsListsUnsupportedChain.tsx
index 9f5aaa755..f184685eb 100644
--- a/features/rewards/components/rewardsListContent/RewardsListsUnsupportedChain.tsx
+++ b/features/rewards/components/rewardsListContent/RewardsListsUnsupportedChain.tsx
@@ -1,4 +1,4 @@
-import { FC, useMemo } from 'react';
+import { FC } from 'react';
import { Divider } from '@lidofinance/lido-ui';
import { useConfig } from 'config';
@@ -7,23 +7,15 @@ import { RewardsListEmptyWrapper } from './RewardsListsEmptyStyles';
export const RewardsListsUnsupportedChain: FC = () => {
const {
- config: { supportedChains },
+ config: { defaultChain },
} = useConfig();
- const supportedChainsNames = useMemo(() => {
- // 'Chain ID' array to 'Chain name' array exclude unknown chain id
- const chains = supportedChains.map((id) => CHAINS[id]).filter(Boolean);
- const lastChain = chains.pop();
- // to str
- return [chains.join(', '), lastChain].filter((chain) => chain).join(' or ');
- }, [supportedChains]);
-
return (
<>
- Please switch to {supportedChainsNames} in your wallet to see the
+ Please switch to {CHAINS[defaultChain]} in your wallet to see the
stats.
diff --git a/features/rewards/components/rewardsListHeader/RewardsListHeader.tsx b/features/rewards/components/rewardsListHeader/RewardsListHeader.tsx
index a1d4a1587..9eb952940 100644
--- a/features/rewards/components/rewardsListHeader/RewardsListHeader.tsx
+++ b/features/rewards/components/rewardsListHeader/RewardsListHeader.tsx
@@ -8,7 +8,8 @@ import { RewardsListHeaderStyle } from './styles';
import { TitleStyle } from './styles';
export const RewardsListHeader: FC = () => {
- const { isWalletConnected, isSupportedChain } = useDappStatus();
+ const { isWalletConnected, isSupportedChain, isAccountActiveOnL2 } =
+ useDappStatus();
const { error, data } = useRewardsHistory();
return (
@@ -17,7 +18,9 @@ export const RewardsListHeader: FC = () => {
{!error &&
data &&
data?.events.length > 0 &&
- (!isWalletConnected || (isWalletConnected && isSupportedChain)) && (
+ (!isWalletConnected ||
+ (isWalletConnected && isSupportedChain) ||
+ !isAccountActiveOnL2) && (
<>
diff --git a/features/rewards/features/top-card/top-card.tsx b/features/rewards/features/top-card/top-card.tsx
index 2e44058a5..46ffd394c 100644
--- a/features/rewards/features/top-card/top-card.tsx
+++ b/features/rewards/features/top-card/top-card.tsx
@@ -1,5 +1,7 @@
import { FC, useEffect, useState } from 'react';
+import { CHAINS } from '@lido-sdk/constants';
+import { getConfig } from 'config';
import { StatsWrapper } from 'features/rewards/components/statsWrapper';
import { Stats } from 'features/rewards/components/stats';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
@@ -8,8 +10,10 @@ import { Fallback } from 'shared/wallet';
import { Wallet } from './wallet';
export const TopCard: FC = () => {
+ const { defaultChain } = getConfig();
const [visible, setVisible] = useState(false);
- const { isWalletConnected, isSupportedChain } = useDappStatus();
+ const { isWalletConnected, isSupportedChain, isAccountActiveOnL2 } =
+ useDappStatus();
// fix flash after reload page
useEffect(() => {
@@ -20,7 +24,17 @@ export const TopCard: FC = () => {
return (
<>
- {isWalletConnected && !isSupportedChain ? : }
+ {isWalletConnected && !isSupportedChain ? (
+
+ ) : isAccountActiveOnL2 ? (
+
+ ) : (
+
+ )}
diff --git a/features/stake/stake-form/controls/stake-amount-input.tsx b/features/stake/stake-form/controls/stake-amount-input.tsx
index 00579d707..58a307a9c 100644
--- a/features/stake/stake-form/controls/stake-amount-input.tsx
+++ b/features/stake/stake-form/controls/stake-amount-input.tsx
@@ -5,7 +5,8 @@ import { useStakingLimitWarning } from 'shared/hooks/use-staking-limit-warning';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
export const StakeAmountInput = () => {
- const { isWalletConnected, isDappActive, isDappActiveOnL2 } = useDappStatus();
+ const { isWalletConnected, isDappActive, isAccountActiveOnL2 } =
+ useDappStatus();
const { maxAmount, stakingLimitInfo } = useStakeFormData();
const { limitWarning, limitError } = useStakingLimitWarning(
stakingLimitInfo?.stakeLimitLevel,
@@ -13,7 +14,7 @@ export const StakeAmountInput = () => {
return (
{
- const { isDappActive, isDappActiveOnL2 } = useDappStatus();
+ const { isDappActive, isAccountActiveOnL2 } = useDappStatus();
const { stakingLimitInfo } = useStakeFormData();
return (
{
const { address } = useAccount();
@@ -98,13 +100,30 @@ const WalletComponent: WalletComponentType = (props) => {
};
export const Wallet: WalletComponentType = memo((props) => {
- const { isDappActive } = useDappStatus();
+ const { defaultChain } = getConfig();
+ const { isWalletConnected, isDappActive, isAccountActiveOnL2 } =
+ useDappStatus();
const { showLidoMultichainFallback } = useLidoMultichainFallbackCondition();
if (showLidoMultichainFallback) {
return ;
}
+ if (isAccountActiveOnL2) {
+ return (
+
+ );
+ }
+
+ if (isWalletConnected && !isDappActive) {
+ return (
+
+ );
+ }
+
if (!isDappActive) {
return ;
}
diff --git a/features/withdrawals/claim/form/requests-list/requests-list.tsx b/features/withdrawals/claim/form/requests-list/requests-list.tsx
index 1e7ed79e8..8bb23403c 100644
--- a/features/withdrawals/claim/form/requests-list/requests-list.tsx
+++ b/features/withdrawals/claim/form/requests-list/requests-list.tsx
@@ -8,7 +8,7 @@ import { Wrapper } from './styles';
import { RequestsLoader } from './requests-loader';
export const RequestsList: React.FC = () => {
- const { isWalletConnected, isDappActive } = useDappStatus();
+ const { isWalletConnected, isDappActiveOnL1 } = useDappStatus();
const { isLoading } = useFormState();
const { register } = useFormContext();
const { fields } = useFieldArray({
@@ -19,11 +19,11 @@ export const RequestsList: React.FC = () => {
return ;
}
- if (!isDappActive || fields.length === 0) {
+ if (!isDappActiveOnL1 || fields.length === 0) {
return (
);
}
diff --git a/features/withdrawals/claim/form/submit-button.tsx b/features/withdrawals/claim/form/submit-button.tsx
index f294a5c1d..2d74e0a6a 100644
--- a/features/withdrawals/claim/form/submit-button.tsx
+++ b/features/withdrawals/claim/form/submit-button.tsx
@@ -3,14 +3,16 @@ import { useAccount } from 'wagmi';
import { Button } from '@lidofinance/lido-ui';
import { Zero } from '@ethersproject/constants';
-import { Connect, UnsupportedChainButton } from 'shared/wallet';
+import { Connect, DisabledButton } from 'shared/wallet';
import { FormatToken } from 'shared/formatters/format-token';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
import { useIsSupportedChain } from 'shared/hooks/use-is-supported-chain';
import { isValidationErrorTypeUnhandled } from 'shared/hook-form/validation/validation-error';
import { ClaimFormInputType, useClaimFormData } from '../claim-form-context';
export const SubmitButton = () => {
+ const { isAccountActiveOnL2 } = useDappStatus();
const { isConnected } = useAccount();
const isSupportedChain = useIsSupportedChain();
@@ -21,19 +23,19 @@ export const SubmitButton = () => {
if (!isConnected) return ;
- if (!isSupportedChain) {
- return ;
+ if (!isSupportedChain || isAccountActiveOnL2) {
+ return Claim ;
}
- const claimButtonAmount = ethToClaim.lte(Zero) ? null : (
-
- );
-
const disabled =
(!!errors.requests &&
!isValidationErrorTypeUnhandled(errors.requests.type)) ||
selectedRequests.length === 0;
+ const claimButtonAmount = ethToClaim.lte(Zero) ? null : (
+
+ );
+
return (
{
const stethAddress = useTokenAddress(TOKENS.STETH);
diff --git a/features/withdrawals/claim/wallet/wallet.tsx b/features/withdrawals/claim/wallet/wallet.tsx
index 81b0b7488..cd65a071d 100644
--- a/features/withdrawals/claim/wallet/wallet.tsx
+++ b/features/withdrawals/claim/wallet/wallet.tsx
@@ -1,8 +1,10 @@
import { memo } from 'react';
+import { CHAINS } from '@lido-sdk/constants';
import { Divider } from '@lidofinance/lido-ui';
import { useSDK } from '@lido-sdk/react';
+import { getConfig } from 'config';
import {
WalletWrapperStyled,
WalletMyRequests,
@@ -39,7 +41,9 @@ export const WalletComponent = () => {
};
export const ClaimWallet: WalletComponentType = memo((props) => {
- const { isDappActive } = useDappStatus();
+ const { defaultChain } = getConfig();
+ const { isWalletConnected, isDappActive, isAccountActiveOnL2 } =
+ useDappStatus();
const { showLidoMultichainFallback } = useLidoMultichainFallbackCondition();
if (showLidoMultichainFallback) {
@@ -48,6 +52,25 @@ export const ClaimWallet: WalletComponentType = memo((props) => {
);
}
+ if (isAccountActiveOnL2) {
+ return (
+
+ );
+ }
+
+ if (isWalletConnected && !isDappActive) {
+ return (
+
+ );
+ }
+
if (!isDappActive) {
return ;
}
diff --git a/features/withdrawals/hooks/contract/useRequest.ts b/features/withdrawals/hooks/contract/useRequest.ts
index 82c7d17c3..e8cc75a5f 100644
--- a/features/withdrawals/hooks/contract/useRequest.ts
+++ b/features/withdrawals/hooks/contract/useRequest.ts
@@ -19,7 +19,7 @@ import {
} from 'shared/hooks';
import { useIsMultisig } from 'shared/hooks/useIsMultisig';
import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider';
-import { useApprove } from 'shared/hooks/useApprove';
+import { useApproveOnL1 } from 'shared/hooks/useApproveOnL1';
import { runWithTransactionLogger } from 'utils';
import { isContract } from 'utils/isContract';
@@ -219,7 +219,7 @@ export const useWithdrawalRequest = ({
allowance,
isLoading: loadingUseApprove,
refetch: refetchAllowance,
- } = useApprove(
+ } = useApproveOnL1(
valueBN,
tokenContract.address,
withdrawalQueueAddress,
diff --git a/features/withdrawals/request/form/controls/submit-button-request.tsx b/features/withdrawals/request/form/controls/submit-button-request.tsx
index 59ea3b0be..fd679a3a7 100644
--- a/features/withdrawals/request/form/controls/submit-button-request.tsx
+++ b/features/withdrawals/request/form/controls/submit-button-request.tsx
@@ -9,10 +9,10 @@ import { useFormState } from 'react-hook-form';
import { isValidationErrorTypeUnhandled } from 'shared/hook-form/validation/validation-error';
import { useIsMultisig } from 'shared/hooks/useIsMultisig';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { DisabledButton } from 'shared/wallet';
// conditional render breaks useFormState, so it can't be inside SubmitButton
export const useRequestSubmitButtonProps = (): SubmitButtonRequestProps => {
- const { isDappActiveOnL2 } = useDappStatus();
const { isPaused } = useWithdrawals();
const { isValidating, isSubmitting, errors } =
useFormState({ name: ['requests', 'amount'] });
@@ -21,7 +21,6 @@ export const useRequestSubmitButtonProps = (): SubmitButtonRequestProps => {
loading: isValidating || isSubmitting,
disabled:
isPaused ||
- isDappActiveOnL2 ||
(!!errors.amount && !isValidationErrorTypeUnhandled(errors.amount.type)),
};
};
@@ -35,12 +34,17 @@ export const SubmitButtonRequest = ({
loading,
disabled,
}: SubmitButtonRequestProps) => {
+ const { isAccountActiveOnL2 } = useDappStatus();
const { isMultisig } = useIsMultisig();
const { isTokenLocked } = useRequestFormData();
const buttonTitle = isTokenLocked
? `Unlock tokens ${isMultisig ? 'for' : 'and'} withdrawal`
: 'Request withdrawal';
+ if (isAccountActiveOnL2) {
+ return Request withdrawal ;
+ }
+
return (
{
- const { isWalletConnected, isDappActive, isDappActiveOnL2 } = useDappStatus();
+ const { isWalletConnected, isDappActive, isAccountActiveOnL2 } =
+ useDappStatus();
const token = useWatch({ name: 'token' });
const { maxAmount, isTokenLocked } = useRequestFormData();
@@ -21,7 +22,7 @@ export const TokenAmountInputRequest = () => {
return (
{
- const { isWalletConnected, isDappActive, isDappActiveOnL2 } = useDappStatus();
+ const { isWalletConnected, isDappActive, isAccountActiveOnL2 } =
+ useDappStatus();
return (
);
diff --git a/features/withdrawals/request/wallet/wallet.tsx b/features/withdrawals/request/wallet/wallet.tsx
index 1f3eecb45..1834f6674 100644
--- a/features/withdrawals/request/wallet/wallet.tsx
+++ b/features/withdrawals/request/wallet/wallet.tsx
@@ -1,10 +1,11 @@
import { memo } from 'react';
import { useWatch } from 'react-hook-form';
-import { TOKENS } from '@lido-sdk/constants';
+import { CHAINS, TOKENS } from '@lido-sdk/constants';
import { Divider } from '@lidofinance/lido-ui';
import { useSDK } from '@lido-sdk/react';
+import { getConfig } from 'config';
import { WalletMyRequests } from 'features/withdrawals/shared';
import { WalletWrapperStyled } from 'features/withdrawals/shared';
import {
@@ -43,7 +44,9 @@ export const WalletComponent = () => {
};
export const RequestWallet: WalletComponentType = memo((props) => {
- const { isDappActive } = useDappStatus();
+ const { defaultChain } = getConfig();
+ const { isWalletConnected, isDappActive, isAccountActiveOnL2 } =
+ useDappStatus();
const { showLidoMultichainFallback } = useLidoMultichainFallbackCondition();
if (showLidoMultichainFallback) {
@@ -55,6 +58,25 @@ export const RequestWallet: WalletComponentType = memo((props) => {
);
}
+ if (isAccountActiveOnL2) {
+ return (
+
+ );
+ }
+
+ if (isWalletConnected && !isDappActive) {
+ return (
+
+ );
+ }
+
if (!isDappActive) {
return ;
}
diff --git a/features/wsteth/shared/hooks/use-debounced-wsteth-steth.ts b/features/wsteth/shared/hooks/use-debounced-wsteth-steth.ts
index 69d2c0853..00f751042 100644
--- a/features/wsteth/shared/hooks/use-debounced-wsteth-steth.ts
+++ b/features/wsteth/shared/hooks/use-debounced-wsteth-steth.ts
@@ -1,60 +1,82 @@
import { Zero } from '@ethersproject/constants';
import type { BigNumber } from 'ethers';
-import { useStethByWsteth } from 'shared/hooks';
import { useDebouncedValue } from 'shared/hooks/useDebouncedValue';
+import { useStethByWsteth } from 'shared/hooks/useStethByWsteth';
+import { useStETHByWstETHOnL2 } from 'shared/hooks/use-stETH-by-wstETH-on-l2';
import { useWstethBySteth } from 'shared/hooks/useWstethBySteth';
+import { useWstETHByStETHOnL2 } from 'shared/hooks/use-wstETH-by-stETH-on-l2';
export const useDebouncedWstethBySteth = (
amount: BigNumber | null,
+ isL2 = false,
delay = 500,
) => {
const fallbackedAmount = amount ?? Zero;
const amountDebounced = useDebouncedValue(fallbackedAmount, delay);
- const swr = useWstethBySteth(amountDebounced ?? undefined);
const isActualValue = fallbackedAmount.eq(amountDebounced);
+
+ const swrL1 = useWstethBySteth(
+ !isL2 && amountDebounced ? amountDebounced : undefined,
+ );
+ const swrL2 = useWstETHByStETHOnL2(
+ isL2 && amountDebounced ? amountDebounced : undefined,
+ );
+
+ const { data, initialLoading, loading, error, update } = isL2 ? swrL2 : swrL1;
+
return {
get data() {
- return isActualValue ? swr.data : undefined;
+ return isActualValue ? data : undefined;
},
get initialLoading() {
- return isActualValue ? swr.initialLoading : true;
+ return isActualValue ? initialLoading : true;
},
get loading() {
- return swr.loading;
+ return loading;
},
get error() {
- return swr.error;
+ return error;
},
get update() {
- return swr.update;
+ return update;
},
};
};
export const useDebouncedStethByWsteth = (
amount: BigNumber | null,
+ isL2 = false,
delay = 500,
) => {
const fallbackedAmount = amount ?? Zero;
const amountDebounced = useDebouncedValue(fallbackedAmount, delay);
- const swr = useStethByWsteth(amountDebounced ?? undefined);
const isActualValue = fallbackedAmount.eq(amountDebounced);
+
+ const swrL1 = useStethByWsteth(
+ !isL2 && amountDebounced ? amountDebounced : undefined,
+ );
+ const swrL2 = useStETHByWstETHOnL2(
+ isL2 && amountDebounced ? amountDebounced : undefined,
+ );
+
+ const { data, initialLoading, loading, error, update } = isL2 ? swrL2 : swrL1;
+
return {
get data() {
- return isActualValue ? swr.data : undefined;
+ return isActualValue ? data : undefined;
},
get initialLoading() {
- return isActualValue ? swr.initialLoading : true;
+ return isActualValue ? initialLoading : true;
},
get loading() {
- return swr.loading;
+ return loading;
},
get error() {
- return swr.error;
+ return error;
},
get update() {
- return swr.update;
+ return update;
},
};
};
diff --git a/features/wsteth/shared/wallet/styles.tsx b/features/wsteth/shared/wallet/styles.tsx
index c4e9a5bd3..c24fb41c6 100644
--- a/features/wsteth/shared/wallet/styles.tsx
+++ b/features/wsteth/shared/wallet/styles.tsx
@@ -1,6 +1,13 @@
import { Card } from 'shared/wallet';
import styled from 'styled-components';
-export const StyledCard = styled(Card)`
- background: linear-gradient(52.01deg, #1b3349 0%, #25697e 100%);
+interface StyledCardProps {
+ $redBg?: boolean;
+}
+
+export const StyledCard = styled(Card)`
+ background: ${({ $redBg }) =>
+ $redBg
+ ? 'linear-gradient(52.01deg, #37394A 0%, #1D1E35 0.01%, #B73544 100%)'
+ : 'linear-gradient(52.01deg, #1b3349 0%, #25697e 100%)'};
`;
diff --git a/features/wsteth/shared/wallet/wallet.tsx b/features/wsteth/shared/wallet/wallet.tsx
index 175e670da..34cd83e0d 100644
--- a/features/wsteth/shared/wallet/wallet.tsx
+++ b/features/wsteth/shared/wallet/wallet.tsx
@@ -1,8 +1,8 @@
import { memo } from 'react';
+import { useAccount } from 'wagmi';
import { Divider, Text } from '@lidofinance/lido-ui';
-import { TOKENS } from '@lido-sdk/constants';
-import { useSDK, useTokenAddress } from '@lido-sdk/react';
+import { useSDK } from '@lido-sdk/react';
import { FormatToken } from 'shared/formatters';
import { TokenToWallet } from 'shared/components';
@@ -17,28 +17,52 @@ import {
Fallback,
LidoMultichainFallback,
} from 'shared/wallet';
-
-import { StyledCard } from './styles';
import {
useEthereumBalance,
useStethBalance,
useWstethBalance,
} from 'shared/hooks/use-balance';
+import { OPTIMISM, ETHEREUM, useDappChain } from 'providers/dapp-chain';
+import { capitalizeFirstLetter } from 'utils/capitalize-string';
+
+import { StyledCard } from './styles';
+import { useStETHByWstETHOnL2 } from 'shared/hooks/use-stETH-by-wstETH-on-l2';
+import { useWstETHByStETHOnL2 } from 'shared/hooks/use-wstETH-by-stETH-on-l2';
const WalletComponent: WalletComponentType = (props) => {
const { account } = useSDK();
+ const { isAccountActiveOnL2, isDappActiveOnL2 } = useDappStatus();
const ethBalance = useEthereumBalance();
const stethBalance = useStethBalance();
const wstethBalance = useWstethBalance();
- const stethAddress = useTokenAddress(TOKENS.STETH);
- const wstethAddress = useTokenAddress(TOKENS.WSTETH);
+ // TODO merge those hooks and only fetch current chain
+ const wstethByStethOnL1 = useWstethBySteth(
+ !isAccountActiveOnL2 && stethBalance.data ? stethBalance.data : undefined,
+ );
+ const wstethByStethOnL2 = useWstETHByStETHOnL2(
+ isAccountActiveOnL2 && stethBalance.data ? stethBalance.data : undefined,
+ );
+ const wstethBySteth = isAccountActiveOnL2
+ ? wstethByStethOnL2
+ : wstethByStethOnL1;
- const wstethBySteth = useWstethBySteth(stethBalance.data);
- const stethByWsteth = useStethByWsteth(wstethBalance.data);
+ const stethByWstethOnL1 = useStethByWsteth(
+ !isAccountActiveOnL2 && wstethBalance.data ? wstethBalance.data : undefined,
+ );
+ const stethByWstethOnL2 = useStETHByWstETHOnL2(
+ isAccountActiveOnL2 && wstethBalance.data ? wstethBalance.data : undefined,
+ );
+ const stethByWsteth = isAccountActiveOnL2
+ ? stethByWstethOnL2
+ : stethByWstethOnL1;
return (
-
+
{
/>
{
/>
{
};
export const Wallet: WalletComponentType = memo((props) => {
- const { isDappActive } = useDappStatus();
+ const { chainId } = useAccount();
+ const { isDappActive, isDappActiveOnL2 } = useDappStatus();
const { showLidoMultichainFallback } = useLidoMultichainFallbackCondition();
+ const { chainName, isMatchDappChainAndWalletChain } = useDappChain();
+
+ if (isDappActive && !isMatchDappChainAndWalletChain(chainId)) {
+ const error = `Wrong network. Please switch to ${chainName === OPTIMISM ? capitalizeFirstLetter(OPTIMISM) : capitalizeFirstLetter(ETHEREUM)} in your wallet to wrap/unwrap.`;
+ return ;
+ }
- if (showLidoMultichainFallback) {
+ if (!isDappActiveOnL2 && showLidoMultichainFallback) {
return ;
}
diff --git a/features/wsteth/shared/wrap-faq/list/do_i_need_to_unwrap_my_wsteth.tsx b/features/wsteth/shared/wrap-faq/list/do_i_need_to_unwrap_my_wsteth.tsx
index a7439b4ca..935db8fac 100644
--- a/features/wsteth/shared/wrap-faq/list/do_i_need_to_unwrap_my_wsteth.tsx
+++ b/features/wsteth/shared/wrap-faq/list/do_i_need_to_unwrap_my_wsteth.tsx
@@ -22,7 +22,7 @@ export const DoINeedToUnwrapMyWsteth: FC = () => {
>
Withdrawals Request and Claim tabs
- . Note that, under the hood, wstETH will unwrap to stETH first, so your
+ Note that, under the hood, wstETH will unwrap to stETH first, so your
request will be denominated in stETH.
diff --git a/features/wsteth/shared/wrap-faq/list/how-can-i-get-wsteth.tsx b/features/wsteth/shared/wrap-faq/list/how-can-i-get-wsteth.tsx
index d9d916e53..b2abf4baa 100644
--- a/features/wsteth/shared/wrap-faq/list/how-can-i-get-wsteth.tsx
+++ b/features/wsteth/shared/wrap-faq/list/how-can-i-get-wsteth.tsx
@@ -30,6 +30,7 @@ export const HowCanIGetWsteth: FC = () => {
>
DEX Lido integrations
+ .
);
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/can-i-stake-my-eth-directly-on-optimism.tsx b/features/wsteth/shared/wrap-faq/optimism-list/can-i-stake-my-eth-directly-on-optimism.tsx
new file mode 100644
index 000000000..aeacc1d99
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/can-i-stake-my-eth-directly-on-optimism.tsx
@@ -0,0 +1,13 @@
+import { FC } from 'react';
+import { Accordion } from '@lidofinance/lido-ui';
+
+export const CanIStakeMyETHDirectlyOnOptimism: FC = () => {
+ return (
+
+
+ No, staking in the Lido Protocol is available only on the Ethereum
+ mainnet.
+
+
+ );
+};
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/do-i-need-to-claim-my-staking-rewards-if-i-wrap-steth-to-wsteth-on-optimism.tsx b/features/wsteth/shared/wrap-faq/optimism-list/do-i-need-to-claim-my-staking-rewards-if-i-wrap-steth-to-wsteth-on-optimism.tsx
new file mode 100644
index 000000000..93097d03f
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/do-i-need-to-claim-my-staking-rewards-if-i-wrap-steth-to-wsteth-on-optimism.tsx
@@ -0,0 +1,11 @@
+import { FC } from 'react';
+import { Accordion } from '@lidofinance/lido-ui';
+
+export const DoINeedToClaimMyStakingRewardsIfIWrapStETHToWstETHOnOptimism: FC =
+ () => {
+ return (
+
+ No, staking rewards accrue to wstETH automatically.
+
+ );
+ };
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/do-i-still-get-staking-rewards-with-steth-or-wsteth-on-optimism.tsx b/features/wsteth/shared/wrap-faq/optimism-list/do-i-still-get-staking-rewards-with-steth-or-wsteth-on-optimism.tsx
new file mode 100644
index 000000000..f41b5955c
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/do-i-still-get-staking-rewards-with-steth-or-wsteth-on-optimism.tsx
@@ -0,0 +1,17 @@
+import { FC } from 'react';
+import { Accordion } from '@lidofinance/lido-ui';
+
+export const DoIStillGetStakingRewardsWithStETHOrWstETHOnOptimism: FC = () => {
+ return (
+
+
+ Yes, wrapped stETH gets staking rewards at the same rate as regular
+ stETH. When you keep your stETH in a wrapper, you cannot see your daily
+ staking rewards. However, when you unwrap your wstETH your new stETH
+ balance will have increased relative to the pre-wrapped amount to
+ reflect your received rewards, regardless of whether it's held on
+ Ethereum or Optimism.
+
+
+ );
+};
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/how-can-i-get-wsteth.tsx b/features/wsteth/shared/wrap-faq/optimism-list/how-can-i-get-wsteth.tsx
new file mode 100644
index 000000000..24d367cad
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/how-can-i-get-wsteth.tsx
@@ -0,0 +1,48 @@
+import { FC } from 'react';
+import { Accordion, Link, Link as OuterLink } from '@lidofinance/lido-ui';
+
+import { config } from 'config';
+import { WRAP_PATH } from 'consts/urls';
+import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events';
+import { trackMatomoEvent } from 'utils/track-matomo-event';
+import { LocalLink } from 'shared/components/local-link';
+
+export const HowCanIGetWstethOnOptimism: FC = () => {
+ return (
+
+
+ You can wrap your stETH tokens using the{' '}
+
+ trackMatomoEvent(
+ MATOMO_CLICK_EVENTS_TYPES.faqHowCanIGetWstethOnOptimismWrapLink,
+ )
+ }
+ aria-hidden="true"
+ >
+ Wrap & Unwrap staking widget
+ {' '}
+ on Optimism,{' '}
+
+ bridge your wstETH from Ethereum to Optimism
+
+ , or use the{' '}
+
+ DEX Lido integrations
+
+ .
+
+
+ );
+};
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/how-can-i-use-wsteth-on-optimism.tsx b/features/wsteth/shared/wrap-faq/optimism-list/how-can-i-use-wsteth-on-optimism.tsx
new file mode 100644
index 000000000..d695ccc11
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/how-can-i-use-wsteth-on-optimism.tsx
@@ -0,0 +1,24 @@
+import { FC } from 'react';
+import { Accordion, Link } from '@lidofinance/lido-ui';
+
+import { config } from 'config';
+import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events';
+
+export const HowCanIUseWstethOnOptimism: FC = () => {
+ return (
+
+
+ wstETH is useful across{' '}
+
+ DeFi protocols
+
+ , which are based on constant balance tokens.
+
+
+ );
+};
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/how-could-i-unwrap-wsteth-back-to-steth-on-optimism.tsx b/features/wsteth/shared/wrap-faq/optimism-list/how-could-i-unwrap-wsteth-back-to-steth-on-optimism.tsx
new file mode 100644
index 000000000..e070511e1
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/how-could-i-unwrap-wsteth-back-to-steth-on-optimism.tsx
@@ -0,0 +1,28 @@
+import { FC } from 'react';
+import { Accordion } from '@lidofinance/lido-ui';
+import { WRAP_UNWRAP_PATH } from 'consts/urls';
+import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events';
+import { LocalLink } from 'shared/components/local-link';
+import { trackMatomoEvent } from 'utils/track-matomo-event';
+
+export const HowCouldIUnwrapWstETHBackToStETHOnOptimism: FC = () => {
+ return (
+
+
+ You can unwrap your wstETH tokens using{' '}
+
+ trackMatomoEvent(
+ MATOMO_CLICK_EVENTS_TYPES.faqHowCouldIUnwrapWstETHBackToStETHOnOptimismUnwrapLink,
+ )
+ }
+ aria-hidden="true"
+ >
+ Wrap & Unwrap staking widget
+
+ .
+
+
+ );
+};
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/index.ts b/features/wsteth/shared/wrap-faq/optimism-list/index.ts
new file mode 100644
index 000000000..48a18cedc
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/index.ts
@@ -0,0 +1,8 @@
+export * from './what-is-wsteth';
+export * from './how-can-i-get-wsteth';
+export * from './how-can-i-use-wsteth-on-optimism';
+export * from './can-i-stake-my-eth-directly-on-optimism';
+export * from './do-i-still-get-staking-rewards-with-steth-or-wsteth-on-optimism';
+export * from './do-i-need-to-claim-my-staking-rewards-if-i-wrap-steth-to-wsteth-on-optimism';
+export * from './how-could-i-unwrap-wsteth-back-to-steth-on-optimism';
+export * from './what-happens-if-i-want-to-unstake-eth-on-ethereum-can-i-do-that-from-optimism';
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/what-happens-if-i-want-to-unstake-eth-on-ethereum-can-i-do-that-from-optimism.tsx b/features/wsteth/shared/wrap-faq/optimism-list/what-happens-if-i-want-to-unstake-eth-on-ethereum-can-i-do-that-from-optimism.tsx
new file mode 100644
index 000000000..f6093e9f6
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/what-happens-if-i-want-to-unstake-eth-on-ethereum-can-i-do-that-from-optimism.tsx
@@ -0,0 +1,39 @@
+import { FC } from 'react';
+import { Accordion, Link } from '@lidofinance/lido-ui';
+import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events';
+import { WITHDRAWALS_REQUEST_PATH } from 'consts/urls';
+import { LocalLink } from 'shared/components/local-link';
+import { trackMatomoEvent } from 'utils/track-matomo-event';
+
+export const WhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimism: FC =
+ () => {
+ return (
+
+
+ You would need to{' '}
+
+ bridge your wstETH or stETH back
+ {' '}
+ to Ethereum mainnet first. Once on the mainnet, you can transform your
+ wstETH or stETH to ETH using the{' '}
+
+ trackMatomoEvent(
+ MATOMO_CLICK_EVENTS_TYPES.faqWhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimismWithdrawalsRequestAndClaim,
+ )
+ }
+ aria-hidden="true"
+ >
+ Withdrawals Request and Claim
+ {' '}
+ tabs.
+
+
+ );
+ };
diff --git a/features/wsteth/shared/wrap-faq/optimism-list/what-is-wsteth.tsx b/features/wsteth/shared/wrap-faq/optimism-list/what-is-wsteth.tsx
new file mode 100644
index 000000000..26eced1f9
--- /dev/null
+++ b/features/wsteth/shared/wrap-faq/optimism-list/what-is-wsteth.tsx
@@ -0,0 +1,17 @@
+import { FC } from 'react';
+import { Accordion } from '@lidofinance/lido-ui';
+
+export const WhatIsWstethOnOptimism: FC = () => {
+ return (
+
+
+ wstETH (wrapped stETH) is a non-rebaseable version of stETH,
+ wstETH's price denominated in stETH changes instead. The wstETH
+ balance can only be changed upon transfers, minting, and burning. At any
+ given time, anyone holding wstETH can convert any amount of it to stETH
+ at a fixed rate, and vice versa. Normally, the rate gets updated once a
+ day, when stETH undergoes a rebase.
+
+
+ );
+};
diff --git a/features/wsteth/shared/wrap-faq/wrap-faq.tsx b/features/wsteth/shared/wrap-faq/wrap-faq.tsx
index e213dadbe..c4bd67600 100644
--- a/features/wsteth/shared/wrap-faq/wrap-faq.tsx
+++ b/features/wsteth/shared/wrap-faq/wrap-faq.tsx
@@ -1,5 +1,7 @@
import { Section } from 'shared/components';
import { useMatomoEventHandle } from 'shared/hooks';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { useDappChain, OPTIMISM } from 'providers/dapp-chain';
import {
WhatIsWsteth,
@@ -11,9 +13,37 @@ import {
DoINeedToUnwrapMyWsteth,
} from './list';
+import {
+ WhatIsWstethOnOptimism,
+ HowCanIGetWstethOnOptimism,
+ HowCanIUseWstethOnOptimism,
+ CanIStakeMyETHDirectlyOnOptimism,
+ DoIStillGetStakingRewardsWithStETHOrWstETHOnOptimism,
+ DoINeedToClaimMyStakingRewardsIfIWrapStETHToWstETHOnOptimism,
+ HowCouldIUnwrapWstETHBackToStETHOnOptimism,
+ WhatHappensIfIWantToUnstakeETHOnEthereumCanIDoThatFromOptimism,
+} from './optimism-list';
+
export const WrapFaq = () => {
+ const { chainName } = useDappChain();
+ const { isWalletConnected } = useDappStatus();
const onClickHandler = useMatomoEventHandle();
+ if (isWalletConnected && chainName === OPTIMISM) {
+ return (
+
+ );
+ }
+
return (
diff --git a/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx b/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx
index 3d56a8dc4..ed996c40f 100644
--- a/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx
+++ b/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx
@@ -9,6 +9,12 @@ import { TxStageOperationSucceedBalanceShown } from 'shared/transaction-modal/tx
import type { BigNumber } from 'ethers';
+const STAGE_APPROVE_ARGS = {
+ token: 'wstETH',
+ willReceiveToken: 'wstETH',
+ operationText: 'Unlocking',
+};
+
const STAGE_OPERATION_ARGS = {
token: 'wstETH',
willReceiveToken: 'stETH',
@@ -20,6 +26,21 @@ const getTxModalStagesUnwrap = (
) => ({
...getGeneralTransactionModalStages(transitStage),
+ signApproval: (amount: BigNumber) =>
+ transitStage(
+ ,
+ ),
+
+ pendingApproval: (amount: BigNumber, txHash?: string) =>
+ transitStage(
+ ,
+ ),
+
sign: (amount: BigNumber, willReceive: BigNumber) =>
transitStage(
;
type UseUnwrapFormProcessorArgs = {
+ approvalDataOnL2: UnwrapFormApprovalData;
onConfirm: () => Promise;
onRetry?: () => void;
};
export const useUnwrapFormProcessor = ({
+ approvalDataOnL2,
onConfirm,
onRetry,
}: UseUnwrapFormProcessorArgs) => {
@@ -31,58 +42,105 @@ export const useUnwrapFormProcessor = ({
const { providerWeb3 } = useSDK();
const { staticRpcProvider } = useCurrentStaticRpcProvider();
const { txModalStages } = useTxModalStagesUnwrap();
- const stETHContractRPC = useSTETHContractRPC();
const wstETHContractRPC = useWSTETHContractRPC();
const wstethContractWeb3 = useWSTETHContractWeb3();
const waitForTx = useTxConfirmation();
+ const isContract = useGetIsContract();
+ const { l2, stETH, isL2 } = useLidoSDK();
+ const { isAccountActiveOnL2 } = useDappStatus();
+
+ const {
+ isApprovalNeededBeforeUnwrap: isApprovalNeededBeforeUnwrapOnL2,
+ processApproveTx: processApproveTxOnL2,
+ } = approvalDataOnL2;
return useCallback(
async ({ amount }: UnwrapFormInputType) => {
try {
invariant(amount, 'amount should be presented');
invariant(address, 'address should be presented');
- invariant(providerWeb3, 'providerWeb3 must be presented');
- invariant(wstethContractWeb3, 'must have wstethContractWeb3');
+ if (!isL2) {
+ invariant(providerWeb3, 'providerWeb3 must be presented');
+ invariant(wstethContractWeb3, 'must have wstethContractWeb3');
+ }
const [isMultisig, willReceive] = await Promise.all([
- isContract(address, staticRpcProvider),
- wstETHContractRPC.getStETHByWstETH(amount),
+ isContract(address),
+ isL2
+ ? l2.steth
+ .convertToSteth(amount.toBigInt())
+ .then(convertToBigNumber)
+ : wstETHContractRPC.getStETHByWstETH(amount),
]);
+ if (isL2 && isApprovalNeededBeforeUnwrapOnL2) {
+ txModalStages.signApproval(amount);
+
+ await processApproveTxOnL2({
+ onTxSent: (txHash) => {
+ if (!isMultisig) {
+ txModalStages.pendingApproval(amount, txHash);
+ }
+ },
+ });
+
+ if (isMultisig) {
+ txModalStages.successMultisig();
+ return true;
+ }
+ }
+
txModalStages.sign(amount, willReceive);
- const txHash = await runWithTransactionLogger(
- 'Unwrap signing',
- async () => {
- const tx =
- await wstethContractWeb3.populateTransaction.unwrap(amount);
-
- return sendTx({
- tx,
- isMultisig,
- staticProvider: staticRpcProvider,
- walletProvider: providerWeb3,
- });
- },
- );
+ let txHash: string;
+ if (isL2) {
+ txHash = (
+ await runWithTransactionLogger('Unwrap signing on L2', () =>
+ // The operation 'wstETH to stETH' on L2 is 'wrap'
+ l2.wrapWstethToSteth({
+ value: amount.toBigInt(),
+ callback: ({ stage, payload }) => {
+ if (stage === TransactionCallbackStage.RECEIPT)
+ txModalStages.pending(amount, willReceive, payload);
+ },
+ }),
+ )
+ ).hash;
+ } else {
+ txHash = await runWithTransactionLogger(
+ 'Unwrap signing on L1',
+ async () => {
+ const tx =
+ await wstethContractWeb3!.populateTransaction.unwrap(amount);
+
+ return sendTx({
+ tx,
+ isMultisig,
+ staticProvider: staticRpcProvider,
+ walletProvider: providerWeb3!,
+ });
+ },
+ );
+ if (!isMultisig) txModalStages.pending(amount, willReceive, txHash);
+ }
if (isMultisig) {
txModalStages.successMultisig();
return true;
}
- txModalStages.pending(amount, willReceive, txHash);
-
await runWithTransactionLogger('Unwrap block confirmation', () =>
waitForTx(txHash),
);
const [stethBalance] = await Promise.all([
- stETHContractRPC.balanceOf(address),
+ isAccountActiveOnL2
+ ? l2.steth.balance(address)
+ : stETH.balance(address),
onConfirm(),
]);
- txModalStages.success(stethBalance, txHash);
+ txModalStages.success(BigNumber.from(stethBalance), txHash);
return true;
} catch (error: any) {
console.warn(error);
@@ -92,13 +150,19 @@ export const useUnwrapFormProcessor = ({
},
[
address,
- providerWeb3,
- wstethContractWeb3,
- staticRpcProvider,
+ isL2,
+ isContract,
+ l2,
wstETHContractRPC,
+ isApprovalNeededBeforeUnwrapOnL2,
txModalStages,
- stETHContractRPC,
+ isAccountActiveOnL2,
+ stETH,
onConfirm,
+ providerWeb3,
+ wstethContractWeb3,
+ processApproveTxOnL2,
+ staticRpcProvider,
waitForTx,
onRetry,
],
diff --git a/features/wsteth/unwrap/hooks/use-unwrap-gas-limit.ts b/features/wsteth/unwrap/hooks/use-unwrap-gas-limit.ts
index 00f3eafcb..b1f870a4d 100644
--- a/features/wsteth/unwrap/hooks/use-unwrap-gas-limit.ts
+++ b/features/wsteth/unwrap/hooks/use-unwrap-gas-limit.ts
@@ -1,33 +1,40 @@
-import { useLidoSWR, useWSTETHContractRPC } from '@lido-sdk/react';
+import { useLidoSWR } from '@lido-sdk/react';
import { config } from 'config';
-import { UNWRAP_GAS_LIMIT } from 'consts/tx';
+import { UNWRAP_GAS_LIMIT, UNWRAP_L2_GAS_LIMIT } from 'consts/tx';
import { STRATEGY_LAZY } from 'consts/swr-strategies';
-import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider';
+import { useLidoSDK } from 'providers/lido-sdk';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { BigNumber } from 'ethers';
export const useUnwrapGasLimit = () => {
- const wsteth = useWSTETHContractRPC();
- const { chainId } = useCurrentStaticRpcProvider();
+ const { isDappActiveOnL2 } = useDappStatus();
+ const { l2, isL2, wrap, core } = useLidoSDK();
+
+ const fallback = isDappActiveOnL2 ? UNWRAP_L2_GAS_LIMIT : UNWRAP_GAS_LIMIT;
const { data } = useLidoSWR(
- ['swr:unwrap-gas-limit', chainId],
- async (_key, chainId) => {
- if (!chainId) return;
+ ['swr:unwrap-gas-limit', isDappActiveOnL2, core.chainId],
+ async () => {
try {
- const gasLimit = await wsteth.estimateGas.unwrap(
- config.ESTIMATE_AMOUNT,
+ const contract = await (isL2
+ ? l2.getContract()
+ : wrap.getContractWstETH());
+
+ const gas = await contract.estimateGas.unwrap(
+ [config.ESTIMATE_AMOUNT.toBigInt()],
{
- from: config.ESTIMATE_ACCOUNT,
+ account: config.ESTIMATE_ACCOUNT,
},
);
- return gasLimit;
+ return BigNumber.from(gas);
} catch (error) {
console.warn(error);
- return UNWRAP_GAS_LIMIT;
+ return fallback;
}
},
STRATEGY_LAZY,
);
- return data ?? UNWRAP_GAS_LIMIT;
+ return data ?? fallback;
};
diff --git a/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts b/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts
new file mode 100644
index 000000000..f4db183d9
--- /dev/null
+++ b/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts
@@ -0,0 +1,77 @@
+import { useMemo, useCallback } from 'react';
+import type { BigNumber } from 'ethers';
+
+import { runWithTransactionLogger } from 'utils';
+import { useLidoSDK } from 'providers/lido-sdk';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { useAllowance } from 'shared/hooks/use-allowance';
+import { useAccount } from 'wagmi';
+import { LIDO_L2_CONTRACT_ADDRESSES } from '@lidofinance/lido-ethereum-sdk/common';
+import { TransactionCallbackStage } from '@lidofinance/lido-ethereum-sdk/core';
+
+type UseUnwrapTxApproveArgs = {
+ amount: BigNumber;
+};
+
+export const useUnwrapTxOnL2Approve = ({ amount }: UseUnwrapTxApproveArgs) => {
+ const { address } = useAccount();
+ const { isAccountActiveOnL2 } = useDappStatus();
+ const { core, l2 } = useLidoSDK();
+
+ const staticTokenAddress = LIDO_L2_CONTRACT_ADDRESSES[core.chainId]?.wsteth;
+ const staticSpenderAddress = LIDO_L2_CONTRACT_ADDRESSES[core.chainId]?.steth;
+
+ // only runs on l2
+ const {
+ data: allowance,
+ refetch: refetchAllowance,
+ isLoading: isAllowanceLoading,
+ } = useAllowance({
+ account: isAccountActiveOnL2 ? address : undefined,
+ spender: staticSpenderAddress,
+ token: staticTokenAddress,
+ });
+
+ const isApprovalNeededBeforeUnwrap = allowance && amount > allowance;
+
+ const processApproveTx = useCallback(
+ async ({ onTxSent }: { onTxSent: (txHash: string) => void }) => {
+ const approveTxHash = (
+ await runWithTransactionLogger('Approve signing on L2', () =>
+ l2.approveWstethForWrap({
+ value: amount.toBigInt(),
+ callback: ({ stage, payload }) => {
+ if (stage === TransactionCallbackStage.RECEIPT)
+ onTxSent?.(payload);
+ },
+ }),
+ )
+ ).hash;
+
+ // wait for refetch to settle
+ await refetchAllowance().catch();
+
+ return approveTxHash;
+ },
+ [l2, amount, refetchAllowance],
+ );
+
+ return useMemo(
+ () => ({
+ processApproveTx,
+ refetchAllowance,
+ allowance,
+ isApprovalNeededBeforeUnwrap,
+ isAllowanceLoading,
+ isShowAllowance: isAccountActiveOnL2,
+ }),
+ [
+ processApproveTx,
+ refetchAllowance,
+ isAllowanceLoading,
+ allowance,
+ isApprovalNeededBeforeUnwrap,
+ isAccountActiveOnL2,
+ ],
+ );
+};
diff --git a/features/wsteth/unwrap/unwrap-form-context/types.ts b/features/wsteth/unwrap/unwrap-form-context/types.ts
index 565762c88..4697ed9b8 100644
--- a/features/wsteth/unwrap/unwrap-form-context/types.ts
+++ b/features/wsteth/unwrap/unwrap-form-context/types.ts
@@ -1,6 +1,9 @@
+import type { BigNumber } from 'ethers';
+
import type { useUnwrapFormNetworkData } from '../hooks/use-unwrap-form-network-data';
+import { useUnwrapTxOnL2Approve } from '../hooks/use-unwrap-tx-on-l2-approve';
-import type { BigNumber } from 'ethers';
+export type UnwrapFormApprovalData = ReturnType;
export type UnwrapFormInputType = {
amount: null | BigNumber;
@@ -14,4 +17,5 @@ export type UnwrapFormValidationContext = {
maxAmount?: BigNumber;
};
-export type UnwrapFormDataContextValueType = UnwrapFormNetworkData;
+export type UnwrapFormDataContextValueType = UnwrapFormNetworkData &
+ UnwrapFormApprovalData;
diff --git a/features/wsteth/unwrap/unwrap-form-context/unwrap-form-context.tsx b/features/wsteth/unwrap/unwrap-form-context/unwrap-form-context.tsx
index 324c6467e..814bb30f7 100644
--- a/features/wsteth/unwrap/unwrap-form-context/unwrap-form-context.tsx
+++ b/features/wsteth/unwrap/unwrap-form-context/unwrap-form-context.tsx
@@ -5,11 +5,11 @@ import {
useMemo,
createContext,
useContext,
+ useCallback,
} from 'react';
import { useForm, FormProvider } from 'react-hook-form';
-import { useUnwrapFormNetworkData } from '../hooks/use-unwrap-form-network-data';
-import { useUnwrapFormProcessor } from '../hooks/use-unwrap-form-processing';
-import { useUnwrapFormValidationContext } from '../hooks/use-unwra-form-validation-context';
+import { Zero } from '@ethersproject/constants';
+
import { useFormControllerRetry } from 'shared/hook-form/form-controller/use-form-controller-retry-delegate';
import {
@@ -17,6 +17,11 @@ import {
FormControllerContextValueType,
} from 'shared/hook-form/form-controller';
+import { useUnwrapFormNetworkData } from '../hooks/use-unwrap-form-network-data';
+import { useUnwrapFormProcessor } from '../hooks/use-unwrap-form-processing';
+import { useUnwrapFormValidationContext } from '../hooks/use-unwra-form-validation-context';
+import { useUnwrapTxOnL2Approve } from '../hooks/use-unwrap-tx-on-l2-approve';
+
import {
UnwrapFormDataContextValueType,
UnwrapFormInputType,
@@ -60,13 +65,33 @@ export const UnwrapFormProvider: FC = ({ children }) => {
resolver: UnwrapFormValidationResolver,
});
+ const { watch } = formObject;
+ const [amount] = watch(['amount']);
const { retryEvent, retryFire } = useFormControllerRetry();
+ const approvalDataOnL2 = useUnwrapTxOnL2Approve({ amount: amount ?? Zero });
+
+ const onConfirm = useCallback(async () => {
+ await Promise.allSettled([
+ networkData.revalidateUnwrapFormData(),
+ approvalDataOnL2.refetchAllowance(),
+ ]);
+ }, [networkData, approvalDataOnL2]);
+
const processUnwrapFormFlow = useUnwrapFormProcessor({
- onConfirm: networkData.revalidateUnwrapFormData,
+ approvalDataOnL2,
+ onConfirm,
onRetry: retryFire,
});
+ const value = useMemo(
+ (): UnwrapFormDataContextValueType => ({
+ ...networkData,
+ ...approvalDataOnL2,
+ }),
+ [networkData, approvalDataOnL2],
+ );
+
const formControllerValue = useMemo(
(): FormControllerContextValueType => ({
onSubmit: processUnwrapFormFlow,
@@ -77,7 +102,7 @@ export const UnwrapFormProvider: FC = ({ children }) => {
return (
-
+
{children}
diff --git a/features/wsteth/unwrap/unwrap-form-controls/submit-button-unwrap.tsx b/features/wsteth/unwrap/unwrap-form-controls/submit-button-unwrap.tsx
index d8dad38fd..de5b10bdc 100644
--- a/features/wsteth/unwrap/unwrap-form-controls/submit-button-unwrap.tsx
+++ b/features/wsteth/unwrap/unwrap-form-controls/submit-button-unwrap.tsx
@@ -1,9 +1,16 @@
import { SubmitButtonHookForm } from 'shared/hook-form/controls/submit-button-hook-form';
+import { useUnwrapFormData } from '../unwrap-form-context';
+
export const SubmitButtonUnwrap = () => {
+ const { isMultisig, isApprovalNeededBeforeUnwrap: isLocked } =
+ useUnwrapFormData();
+
return (
- Unwrap
+ {isLocked
+ ? `Unlock tokens ${isMultisig ? 'to' : 'and'} unwrap`
+ : 'Unwrap'}
);
};
diff --git a/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx b/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx
index 0f9acf234..e27cb6ef8 100644
--- a/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx
+++ b/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx
@@ -1,16 +1,28 @@
import { useWatch } from 'react-hook-form';
+import { BigNumber } from 'ethers';
import { DataTableRow, DataTable } from '@lidofinance/lido-ui';
+import { TOKENS } from '@lido-sdk/constants';
-import { useTxCostInUsd } from 'shared/hooks';
-import { FormatToken } from 'shared/formatters/format-token';
import { DataTableRowStethByWsteth } from 'shared/components/data-table-row-steth-by-wsteth';
+import { AllowanceDataTableRow } from 'shared/components/allowance-data-table-row';
+import { FormatToken } from 'shared/formatters/format-token';
import { FormatPrice } from 'shared/formatters';
+import { useTxCostInUsd } from 'shared/hooks';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
import { useDebouncedStethByWsteth } from 'features/wsteth/shared/hooks/use-debounced-wsteth-steth';
import { useUnwrapGasLimit } from '../hooks/use-unwrap-gas-limit';
-import type { UnwrapFormInputType } from '../unwrap-form-context';
+import { useUnwrapFormData, UnwrapFormInputType } from '../unwrap-form-context';
+import { useApproveGasLimit } from 'features/wsteth/wrap/hooks/use-approve-gas-limit';
export const UnwrapStats = () => {
+ const {
+ isWalletConnected,
+ isAccountActiveOnL2,
+ isDappActiveAndNetworksMatched,
+ } = useDappStatus();
+ const { allowance, isAllowanceLoading, isShowAllowance } =
+ useUnwrapFormData();
const amount = useWatch({ name: 'amount' });
const unwrapGasLimit = useUnwrapGasLimit();
const {
@@ -18,8 +30,14 @@ export const UnwrapStats = () => {
initialLoading: isUnwrapTxCostLoading,
} = useTxCostInUsd(unwrapGasLimit);
+ const approveGasLimit = useApproveGasLimit();
+ const {
+ txCostUsd: approveTxCostInUsd,
+ initialLoading: isApproveCostLoading,
+ } = useTxCostInUsd(approveGasLimit);
+
const { data: willReceiveStETH, initialLoading: isWillReceiveStETHLoading } =
- useDebouncedStethByWsteth(amount);
+ useDebouncedStethByWsteth(amount, isAccountActiveOnL2);
return (
@@ -40,9 +58,34 @@ export const UnwrapStats = () => {
data-testid="maxGasFee"
loading={isUnwrapTxCostLoading}
>
-
+ {isWalletConnected && !isDappActiveAndNetworksMatched ? (
+ '-'
+ ) : (
+
+ )}
+ {isShowAllowance && (
+
+ {isWalletConnected && !isDappActiveAndNetworksMatched ? (
+ '-'
+ ) : (
+
+ )}
+
+ )}
+ {isShowAllowance && (
+
+ )}
);
};
diff --git a/features/wsteth/wrap/hooks/use-approve-gas-limit.tsx b/features/wsteth/wrap/hooks/use-approve-gas-limit.tsx
index 272f4f81c..c3f60d6aa 100644
--- a/features/wsteth/wrap/hooks/use-approve-gas-limit.tsx
+++ b/features/wsteth/wrap/hooks/use-approve-gas-limit.tsx
@@ -1,45 +1,49 @@
import { BigNumber } from 'ethers';
-import { useAccount } from 'wagmi';
-import {
- useLidoSWR,
- useSTETHContractRPC,
- useWSTETHContractRPC,
-} from '@lido-sdk/react';
+import { useLidoSWR } from '@lido-sdk/react';
import { config } from 'config';
-import { WSTETH_APPROVE_GAS_LIMIT } from 'consts/tx';
-import { SDK_LEGACY_SUPPORTED_CHAINS, CHAINS } from 'consts/chains';
-import { STRATEGY_IMMUTABLE } from 'consts/swr-strategies';
+import {
+ STETH_L2_APPROVE_GAS_LIMIT,
+ WSTETH_APPROVE_GAS_LIMIT,
+} from 'consts/tx';
+import { STRATEGY_LAZY } from 'consts/swr-strategies';
+import { useLidoSDK } from 'providers/lido-sdk';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
export const useApproveGasLimit = () => {
- const steth = useSTETHContractRPC();
- const wsteth = useWSTETHContractRPC();
- const { chainId } = useAccount();
+ const { isDappActiveOnL2 } = useDappStatus();
+ const { l2, stETH, isL2, wstETH, core } = useLidoSDK();
- const { data } = useLidoSWR(
- ['swr:approve-wrap-gas-limit', chainId],
- async (_key, chainId) => {
- if (
- !chainId ||
- SDK_LEGACY_SUPPORTED_CHAINS.indexOf(chainId as CHAINS) < 0
- ) {
- return;
- }
+ const fallback = isDappActiveOnL2
+ ? STETH_L2_APPROVE_GAS_LIMIT
+ : WSTETH_APPROVE_GAS_LIMIT;
+ const { data } = useLidoSWR(
+ ['swr:approve-wrap-gas-limit', isDappActiveOnL2, core.chainId],
+ async () => {
try {
- const gasLimit = await steth.estimateGas.approve(
- wsteth.address,
- config.ESTIMATE_AMOUNT,
- { from: config.ESTIMATE_ACCOUNT },
+ // wsteth on l1, steth on l2
+ const spender = await (isL2
+ ? l2.contractAddress()
+ : wstETH.contractAddress());
+
+ // steth on l1, wsteth on l2
+ const contract = await (isL2 ? l2.getContract() : stETH.getContract());
+
+ const gas = await contract.estimateGas.approve(
+ [spender, config.ESTIMATE_AMOUNT.toBigInt()],
+ {
+ account: config.ESTIMATE_ACCOUNT,
+ },
);
- return gasLimit;
+ return BigNumber.from(gas);
} catch (error) {
- console.warn(_key, error);
- return BigNumber.from(WSTETH_APPROVE_GAS_LIMIT);
+ console.warn(error);
+ return fallback;
}
},
- STRATEGY_IMMUTABLE,
+ STRATEGY_LAZY,
);
- return data ?? WSTETH_APPROVE_GAS_LIMIT;
+ return data ?? fallback;
};
diff --git a/features/wsteth/wrap/hooks/use-wrap-form-processing.ts b/features/wsteth/wrap/hooks/use-wrap-form-processing.ts
index ca9c4e423..97d669fc4 100644
--- a/features/wsteth/wrap/hooks/use-wrap-form-processing.ts
+++ b/features/wsteth/wrap/hooks/use-wrap-form-processing.ts
@@ -1,58 +1,76 @@
import { useCallback } from 'react';
+import { BigNumber } from 'ethers';
import invariant from 'tiny-invariant';
import { useAccount } from 'wagmi';
import { useSDK, useWSTETHContractRPC } from '@lido-sdk/react';
+import { TransactionCallbackStage } from '@lidofinance/lido-ethereum-sdk/core';
-import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider';
-import { runWithTransactionLogger } from 'utils';
-import { isContract } from 'utils/isContract';
import { useTxConfirmation } from 'shared/hooks/use-tx-conformation';
+import { useGetIsContract } from 'shared/hooks/use-is-contract';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { runWithTransactionLogger } from 'utils';
+import { convertToBigNumber } from 'utils/convert-to-big-number';
+
+import { useLidoSDK } from 'providers/lido-sdk';
import type {
WrapFormApprovalData,
WrapFormInputType,
} from '../wrap-form-context';
-import { useWrapTxProcessing } from './use-wrap-tx-processing';
+import { useWrapTxOnL1Processing } from './use-wrap-tx-on-l1-processing';
import { useTxModalWrap } from './use-tx-modal-stages-wrap';
type UseWrapFormProcessorArgs = {
- approvalData: WrapFormApprovalData;
+ approvalDataOnL1: WrapFormApprovalData;
onConfirm: () => Promise;
onRetry?: () => void;
};
export const useWrapFormProcessor = ({
- approvalData,
+ approvalDataOnL1,
onConfirm,
onRetry,
}: UseWrapFormProcessorArgs) => {
const { address } = useAccount();
const { providerWeb3 } = useSDK();
- const { staticRpcProvider } = useCurrentStaticRpcProvider();
const wstETHContractRPC = useWSTETHContractRPC();
+ const { l2, isL2, wstETH } = useLidoSDK();
+
+ const { isAccountActiveOnL2 } = useDappStatus();
const { txModalStages } = useTxModalWrap();
- const processWrapTx = useWrapTxProcessing();
+ const processWrapTxOnL1 = useWrapTxOnL1Processing();
+
const waitForTx = useTxConfirmation();
- const { isApprovalNeededBeforeWrap, processApproveTx } = approvalData;
+ const isContract = useGetIsContract();
+ const {
+ isApprovalNeededBeforeWrap: isApprovalNeededBeforeWrapOnL1,
+ processApproveTx: processApproveTxOnL1,
+ } = approvalDataOnL1;
return useCallback(
async ({ amount, token }: WrapFormInputType) => {
try {
+ if (!isL2) {
+ invariant(providerWeb3, 'providerWeb3 should be presented');
+ }
invariant(amount, 'amount should be presented');
invariant(address, 'address should be presented');
- invariant(providerWeb3, 'providerWeb3 should be presented');
const [isMultisig, willReceive] = await Promise.all([
- isContract(address, staticRpcProvider),
- wstETHContractRPC.getWstETHByStETH(amount),
+ isContract(address),
+ isAccountActiveOnL2
+ ? l2.steth
+ .convertToShares(amount.toBigInt())
+ .then(convertToBigNumber)
+ : wstETHContractRPC.getWstETHByStETH(amount),
]);
- if (isApprovalNeededBeforeWrap) {
+ if (isApprovalNeededBeforeWrapOnL1) {
txModalStages.signApproval(amount, token);
- await processApproveTx({
+ await processApproveTxOnL1({
onTxSent: (txHash) => {
if (!isMultisig) {
txModalStages.pendingApproval(amount, token, txHash);
@@ -67,27 +85,46 @@ export const useWrapFormProcessor = ({
txModalStages.sign(amount, token, willReceive);
- const txHash = await runWithTransactionLogger('Wrap signing', () =>
- processWrapTx({ amount, token, isMultisig }),
- );
+ let txHash: string;
+ if (isAccountActiveOnL2) {
+ const txResult = await runWithTransactionLogger(
+ 'Wrap signing on L2',
+ () =>
+ // The operation 'stETH to wstETH' on L2 is 'unwrap'
+ l2.unwrapStethToWsteth({
+ value: amount.toBigInt(),
+ callback: ({ stage, payload }) => {
+ if (stage === TransactionCallbackStage.RECEIPT)
+ txModalStages.pending(amount, token, willReceive, payload);
+ },
+ }),
+ );
+ txHash = txResult.hash;
+ } else {
+ txHash = await runWithTransactionLogger('Wrap signing on L1', () =>
+ processWrapTxOnL1({ amount, token, isMultisig }),
+ );
+ if (!isMultisig)
+ txModalStages.pending(amount, token, willReceive, txHash);
+ }
if (isMultisig) {
txModalStages.successMultisig();
return true;
}
- txModalStages.pending(amount, token, willReceive, txHash);
-
await runWithTransactionLogger('Wrap block confirmation', () =>
waitForTx(txHash),
);
const [wstethBalance] = await Promise.all([
- wstETHContractRPC.balanceOf(address),
+ isAccountActiveOnL2
+ ? l2.wsteth.balance(address)
+ : wstETH.balance(address),
onConfirm(),
]);
- txModalStages.success(wstethBalance, txHash);
+ txModalStages.success(BigNumber.from(wstethBalance), txHash);
return true;
} catch (error) {
console.warn(error);
@@ -96,15 +133,19 @@ export const useWrapFormProcessor = ({
}
},
[
+ isL2,
address,
- providerWeb3,
- staticRpcProvider,
+ isContract,
+ isAccountActiveOnL2,
+ l2,
wstETHContractRPC,
- isApprovalNeededBeforeWrap,
+ isApprovalNeededBeforeWrapOnL1,
txModalStages,
+ wstETH,
onConfirm,
- processApproveTx,
- processWrapTx,
+ providerWeb3,
+ processApproveTxOnL1,
+ processWrapTxOnL1,
waitForTx,
onRetry,
],
diff --git a/features/wsteth/wrap/hooks/use-wrap-gas-limit.ts b/features/wsteth/wrap/hooks/use-wrap-gas-limit.ts
index 4433fc87e..26e792618 100644
--- a/features/wsteth/wrap/hooks/use-wrap-gas-limit.ts
+++ b/features/wsteth/wrap/hooks/use-wrap-gas-limit.ts
@@ -1,29 +1,40 @@
-import { useAccount } from 'wagmi';
-import { useLidoSWR, useWSTETHContractRPC } from '@lido-sdk/react';
+import { useLidoSWR } from '@lido-sdk/react';
import { config } from 'config';
-import { WRAP_FROM_ETH_GAS_LIMIT, WRAP_GAS_LIMIT } from 'consts/tx';
-import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider';
-import { applyGasLimitRatio } from 'utils/apply-gas-limit-ratio';
+import {
+ WRAP_FROM_ETH_GAS_LIMIT,
+ WRAP_GAS_LIMIT,
+ WRAP_L2_GAS_LIMIT,
+} from 'consts/tx';
+import {
+ applyGasLimitRatio,
+ applyGasLimitRatioBigInt,
+} from 'utils/apply-gas-limit-ratio';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { useLidoSDK } from 'providers/lido-sdk';
+import { ESTIMATE_ACCOUNT, ESTIMATE_AMOUNT } from 'config/groups/web3';
+import { BigNumber } from 'ethers';
+import { Zero } from '@ethersproject/constants';
export const useWrapGasLimit = () => {
- const wsteth = useWSTETHContractRPC();
- const { chainId } = useAccount();
- const { staticRpcProvider } = useCurrentStaticRpcProvider();
+ const { isAccountActiveOnL2 } = useDappStatus();
+ const { l2, isL2, wrap, core } = useLidoSDK();
- const { data } = useLidoSWR(
- ['[swr:wrap-gas-limit]', chainId],
- async (_key, chainId) => {
- if (!chainId) return;
+ const wrapFallback = isAccountActiveOnL2 ? WRAP_L2_GAS_LIMIT : WRAP_GAS_LIMIT;
+ const { data } = useLidoSWR(
+ ['[swr:wrap-gas-limit]', core.chainId, isL2],
+ async (_key: string) => {
const fetchGasLimitETH = async () => {
+ if (isL2) return Zero;
try {
- return applyGasLimitRatio(
- await staticRpcProvider.estimateGas({
- from: config.ESTIMATE_ACCOUNT,
- to: wsteth.address,
- value: config.ESTIMATE_AMOUNT,
- }),
+ return BigNumber.from(
+ applyGasLimitRatioBigInt(
+ await wrap.wrapEthEstimateGas({
+ value: ESTIMATE_AMOUNT.toBigInt(),
+ account: ESTIMATE_ACCOUNT,
+ }),
+ ),
);
} catch (error) {
console.warn(`${_key}::[eth]`, error);
@@ -33,12 +44,29 @@ export const useWrapGasLimit = () => {
const fetchGasLimitStETH = async () => {
try {
- return await wsteth.estimateGas.wrap(config.ESTIMATE_AMOUNT, {
- from: config.ESTIMATE_ACCOUNT,
- });
+ if (isL2) {
+ // L2 unwrap steth to wsteth
+ const contract = await l2.getContract();
+ return BigNumber.from(
+ await contract.estimateGas.unwrap([ESTIMATE_AMOUNT.toBigInt()], {
+ account: ESTIMATE_ACCOUNT,
+ }),
+ );
+ } else {
+ // L1 wrap steth to wsteth
+ const contract = await wrap.getContractWstETH();
+ return BigNumber.from(
+ await contract.estimateGas.wrap(
+ [config.ESTIMATE_AMOUNT.toBigInt()],
+ {
+ account: config.ESTIMATE_ACCOUNT,
+ },
+ ),
+ );
+ }
} catch (error) {
console.warn(`${_key}::[steth]`, error);
- return WRAP_GAS_LIMIT;
+ return wrapFallback;
}
};
@@ -56,6 +84,6 @@ export const useWrapGasLimit = () => {
return {
gasLimitETH: data?.gasLimitETH || WRAP_FROM_ETH_GAS_LIMIT,
- gasLimitStETH: data?.gasLimitStETH || WRAP_GAS_LIMIT,
+ gasLimitStETH: data?.gasLimitStETH || wrapFallback,
};
};
diff --git a/features/wsteth/wrap/hooks/use-wrap-tx-approve.ts b/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts
similarity index 71%
rename from features/wsteth/wrap/hooks/use-wrap-tx-approve.ts
rename to features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts
index f8fd6396f..4211e1f74 100644
--- a/features/wsteth/wrap/hooks/use-wrap-tx-approve.ts
+++ b/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts
@@ -1,12 +1,11 @@
import { useMemo } from 'react';
-import { useAccount } from 'wagmi';
import type { BigNumber } from 'ethers';
import { getTokenAddress, TOKENS } from '@lido-sdk/constants';
import { useSDK } from '@lido-sdk/react';
import { TokensWrappable, TOKENS_TO_WRAP } from 'features/wsteth/shared/types';
-import { useApprove } from 'shared/hooks/useApprove';
+import { useApproveOnL1 } from 'shared/hooks/useApproveOnL1';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
type UseWrapTxApproveArgs = {
@@ -14,9 +13,11 @@ type UseWrapTxApproveArgs = {
token: TokensWrappable;
};
-export const useWrapTxApprove = ({ amount, token }: UseWrapTxApproveArgs) => {
- const { isDappActive } = useDappStatus();
- const { address } = useAccount();
+export const useWrapTxOnL1Approve = ({
+ amount,
+ token,
+}: UseWrapTxApproveArgs) => {
+ const { isDappActiveOnL1 } = useDappStatus();
const { chainId } = useSDK();
const [stethTokenAddress, wstethTokenAddress] = useMemo(
@@ -33,15 +34,14 @@ export const useWrapTxApprove = ({ amount, token }: UseWrapTxApproveArgs) => {
allowance,
isLoading: isApprovalLoading,
refetch: refetchAllowance,
- } = useApprove(
+ } = useApproveOnL1(
amount,
- stethTokenAddress,
- wstethTokenAddress,
- address ? address : undefined,
+ isDappActiveOnL1 ? stethTokenAddress : undefined,
+ isDappActiveOnL1 ? wstethTokenAddress : undefined,
);
const isApprovalNeededBeforeWrap =
- isDappActive && needsApprove && token === TOKENS_TO_WRAP.STETH;
+ isDappActiveOnL1 && needsApprove && token === TOKENS_TO_WRAP.STETH;
return useMemo(
() => ({
@@ -51,6 +51,7 @@ export const useWrapTxApprove = ({ amount, token }: UseWrapTxApproveArgs) => {
isApprovalLoading,
isApprovalNeededBeforeWrap,
refetchAllowance,
+ isShowAllowance: isDappActiveOnL1,
}),
[
allowance,
@@ -59,6 +60,7 @@ export const useWrapTxApprove = ({ amount, token }: UseWrapTxApproveArgs) => {
isApprovalLoading,
processApproveTx,
refetchAllowance,
+ isDappActiveOnL1,
],
);
};
diff --git a/features/wsteth/wrap/hooks/use-wrap-tx-processing.ts b/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-processing.ts
similarity index 98%
rename from features/wsteth/wrap/hooks/use-wrap-tx-processing.ts
rename to features/wsteth/wrap/hooks/use-wrap-tx-on-l1-processing.ts
index 00d994ebb..7ec7eadb6 100644
--- a/features/wsteth/wrap/hooks/use-wrap-tx-processing.ts
+++ b/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-processing.ts
@@ -29,7 +29,7 @@ type WrapTxProcessorArgs = WrapFormInputType & {
isMultisig: boolean;
};
-export const useWrapTxProcessing = () => {
+export const useWrapTxOnL1Processing = () => {
const { chainId, providerWeb3 } = useSDK();
const { staticRpcProvider } = useCurrentStaticRpcProvider();
const wstethContractWeb3 = useWSTETHContractWeb3();
diff --git a/features/wsteth/wrap/wrap-form-context/types.ts b/features/wsteth/wrap/wrap-form-context/types.ts
index 76e4720bc..13555c2c1 100644
--- a/features/wsteth/wrap/wrap-form-context/types.ts
+++ b/features/wsteth/wrap/wrap-form-context/types.ts
@@ -1,5 +1,5 @@
import type { useWrapFormNetworkData } from '../hooks/use-wrap-form-network-data';
-import type { useWrapTxApprove } from '../hooks/use-wrap-tx-approve';
+import type { useWrapTxOnL1Approve } from '../hooks/use-wrap-tx-on-l1-approve';
import type { BigNumber } from 'ethers';
import type { TokensWrappable } from 'features/wsteth/shared/types';
@@ -13,7 +13,7 @@ export type WrapFormInputType = {
export type WrapFormNetworkData = ReturnType;
-export type WrapFormApprovalData = ReturnType;
+export type WrapFormApprovalData = ReturnType;
export type WrapFormValidationContext = {
asyncContext: Promise;
diff --git a/features/wsteth/wrap/wrap-form-context/wrap-form-context.tsx b/features/wsteth/wrap/wrap-form-context/wrap-form-context.tsx
index b2bf010d4..8cfc082a3 100644
--- a/features/wsteth/wrap/wrap-form-context/wrap-form-context.tsx
+++ b/features/wsteth/wrap/wrap-form-context/wrap-form-context.tsx
@@ -8,7 +8,7 @@ import {
useCallback,
} from 'react';
import { useForm, FormProvider } from 'react-hook-form';
-import { useWrapTxApprove } from '../hooks/use-wrap-tx-approve';
+import { useWrapTxOnL1Approve } from '../hooks/use-wrap-tx-on-l1-approve';
import { useWrapFormNetworkData } from '../hooks/use-wrap-form-network-data';
import { useWrapFormProcessor } from '../hooks/use-wrap-form-processing';
import { useWrapFormValidationContext } from '../hooks/use-wrap-form-validation-context';
@@ -70,18 +70,21 @@ export const WrapFormProvider: FC = ({ children }) => {
const [token, amount] = watch(['token', 'amount']);
const { retryEvent, retryFire } = useFormControllerRetry();
- const approvalData = useWrapTxApprove({ amount: amount ?? Zero, token });
+ const approvalDataOnL1 = useWrapTxOnL1Approve({
+ amount: amount ?? Zero,
+ token,
+ });
const isSteth = token === TOKENS_TO_WRAP.STETH;
const onConfirm = useCallback(async () => {
await Promise.allSettled([
networkData.revalidateWrapFormData(),
- approvalData.refetchAllowance(),
+ approvalDataOnL1.refetchAllowance(),
]);
- }, [networkData, approvalData]);
+ }, [networkData, approvalDataOnL1]);
const processWrapFormFlow = useWrapFormProcessor({
- approvalData,
+ approvalDataOnL1,
onConfirm,
onRetry: retryFire,
});
@@ -89,7 +92,7 @@ export const WrapFormProvider: FC = ({ children }) => {
const value = useMemo(
(): WrapFormDataContextValueType => ({
...networkData,
- ...approvalData,
+ ...approvalDataOnL1,
isSteth,
stakeLimitInfo: networkData.stakeLimitInfo,
maxAmount: isSteth ? networkData.stethBalance : networkData.maxAmountETH,
@@ -97,7 +100,7 @@ export const WrapFormProvider: FC = ({ children }) => {
? networkData.gasLimitStETH
: networkData.gasLimitETH,
}),
- [networkData, approvalData, isSteth],
+ [networkData, approvalDataOnL1, isSteth],
);
const formControllerValue = useMemo(
diff --git a/features/wsteth/wrap/wrap-form-controls/input-group-wrap.tsx b/features/wsteth/wrap/wrap-form-controls/input-group-wrap.tsx
index 5a8535e9b..8e5517441 100644
--- a/features/wsteth/wrap/wrap-form-controls/input-group-wrap.tsx
+++ b/features/wsteth/wrap/wrap-form-controls/input-group-wrap.tsx
@@ -1,6 +1,8 @@
import { useWatch } from 'react-hook-form';
import { InputGroupHookForm } from 'shared/hook-form/controls/input-group-hook-form';
import { useStakingLimitWarning } from 'shared/hooks/use-staking-limit-warning';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+
import { WrapFormInputType, useWrapFormData } from '../wrap-form-context';
import { TokenAmountInputWrap } from './token-amount-input-wrap';
import { TokenSelectWrap } from './token-select-wrap';
@@ -11,11 +13,13 @@ export const InputGroupWrap: React.FC = () => {
const { limitWarning } = useStakingLimitWarning(
stakeLimitInfo?.stakeLimitLevel,
);
+ const { isDappActiveOnL2 } = useDappStatus();
+
const hasWarning = !!(token === 'ETH' && limitWarning);
const warningText = hasWarning ? limitWarning : null;
return (
-
+ {!isDappActiveOnL2 && }
);
diff --git a/features/wsteth/wrap/wrap-form-controls/submit-button-wrap.tsx b/features/wsteth/wrap/wrap-form-controls/submit-button-wrap.tsx
index 55169b36f..dfbd6c0bb 100644
--- a/features/wsteth/wrap/wrap-form-controls/submit-button-wrap.tsx
+++ b/features/wsteth/wrap/wrap-form-controls/submit-button-wrap.tsx
@@ -1,7 +1,7 @@
-import { useWrapFormData } from '../wrap-form-context';
-
import { SubmitButtonHookForm } from 'shared/hook-form/controls/submit-button-hook-form';
+import { useWrapFormData } from '../wrap-form-context';
+
export const SubmitButtonWrap = () => {
const { isMultisig, isApprovalNeededBeforeWrap: isLocked } =
useWrapFormData();
diff --git a/features/wsteth/wrap/wrap-form-controls/token-amount-input-wrap.tsx b/features/wsteth/wrap/wrap-form-controls/token-amount-input-wrap.tsx
index 1cd45b7b5..8f65cb553 100644
--- a/features/wsteth/wrap/wrap-form-controls/token-amount-input-wrap.tsx
+++ b/features/wsteth/wrap/wrap-form-controls/token-amount-input-wrap.tsx
@@ -1,3 +1,4 @@
+import { Steth } from '@lidofinance/lido-ui';
import { useWatch } from 'react-hook-form';
import { TokenAmountInputHookForm } from 'shared/hook-form/controls/token-amount-input-hook-form';
@@ -11,19 +12,28 @@ type TokenAmountInputWrapProps = Pick<
>;
export const TokenAmountInputWrap = (props: TokenAmountInputWrapProps) => {
- const { isWalletConnected, isDappActive } = useDappStatus();
+ const {
+ isWalletConnected,
+ isAccountActiveOnL2,
+ isDappActiveAndNetworksMatched,
+ } = useDappStatus();
const token = useWatch({ name: 'token' });
const { maxAmount, isApprovalNeededBeforeWrap } = useWrapFormData();
return (
+ ) : undefined
+ }
{...props}
/>
);
diff --git a/features/wsteth/wrap/wrap-form-controls/token-select-wrap.tsx b/features/wsteth/wrap/wrap-form-controls/token-select-wrap.tsx
index dfe7f1511..d77a9ff49 100644
--- a/features/wsteth/wrap/wrap-form-controls/token-select-wrap.tsx
+++ b/features/wsteth/wrap/wrap-form-controls/token-select-wrap.tsx
@@ -1,3 +1,4 @@
+import { useMemo } from 'react';
import { trackEvent } from '@lidofinance/analytics-matomo';
import { TOKENS_TO_WRAP } from 'features/wsteth/shared/types';
@@ -5,16 +6,15 @@ import { MATOMO_CLICK_EVENTS } from 'consts/matomo-click-events';
import { TokenSelectHookForm } from 'shared/hook-form/controls/token-select-hook-form/token-select-hook-form';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
-const OPTIONS = [
- {
- label: 'Lido (stETH)',
- token: TOKENS_TO_WRAP.STETH,
- },
- {
- label: 'Ethereum (ETH)',
- token: TOKENS_TO_WRAP.ETH,
- },
-];
+const OPTION_STETH = {
+ label: 'Lido (stETH)',
+ token: TOKENS_TO_WRAP.STETH,
+};
+
+const OPTION_ETH = {
+ label: 'Ethereum (ETH)',
+ token: TOKENS_TO_WRAP.ETH,
+};
type TokenSelectWrapProps = Pick<
React.ComponentProps,
@@ -22,12 +22,24 @@ type TokenSelectWrapProps = Pick<
>;
export const TokenSelectWrap = (props: TokenSelectWrapProps) => {
- const { isWalletConnected, isDappActive } = useDappStatus();
+ const {
+ isWalletConnected,
+ isDappActiveAndNetworksMatched,
+ isDappActiveOnL2,
+ } = useDappStatus();
+
+ const options = useMemo(() => {
+ if (isDappActiveOnL2) {
+ return [OPTION_STETH];
+ } else {
+ return [OPTION_STETH, OPTION_ETH];
+ }
+ }, [isDappActiveOnL2]);
return (
{
trackEvent(
...(value === TOKENS_TO_WRAP.ETH
diff --git a/features/wsteth/wrap/wrap-form/wrap-stats.tsx b/features/wsteth/wrap/wrap-form/wrap-stats.tsx
index b179f6d11..900bcb670 100644
--- a/features/wsteth/wrap/wrap-form/wrap-stats.tsx
+++ b/features/wsteth/wrap/wrap-form/wrap-stats.tsx
@@ -11,6 +11,7 @@ import { AllowanceDataTableRow } from 'shared/components/allowance-data-table-ro
import { FormatPrice, FormatToken } from 'shared/formatters';
import { useTxCostInUsd, useWstethBySteth } from 'shared/hooks';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { useWstETHByStETHOnL2 } from 'shared/hooks/use-wstETH-by-stETH-on-l2';
import { useApproveGasLimit } from '../hooks/use-approve-gas-limit';
import { useWrapFormData, WrapFormInputType } from '../wrap-form-context';
@@ -18,8 +19,14 @@ import { useWrapFormData, WrapFormInputType } from '../wrap-form-context';
const oneSteth = parseEther('1');
export const WrapFormStats = () => {
- const { isDappActive } = useDappStatus();
- const { allowance, wrapGasLimit, isApprovalLoading } = useWrapFormData();
+ const {
+ isWalletConnected,
+ isDappActive,
+ isDappActiveOnL2,
+ isDappActiveAndNetworksMatched,
+ } = useDappStatus();
+ const { allowance, isShowAllowance, wrapGasLimit, isApprovalLoading } =
+ useWrapFormData();
const { watch } = useFormContext();
const [token, amount] = watch(['token', 'amount']);
@@ -29,12 +36,19 @@ export const WrapFormStats = () => {
const {
data: willReceiveWsteth,
initialLoading: isWillReceiveWstethLoading,
- } = useDebouncedWstethBySteth(amount);
+ } = useDebouncedWstethBySteth(amount, isDappActiveOnL2);
+
+ const wstethBySteth = useWstethBySteth(
+ !isDappActiveOnL2 ? oneSteth : undefined,
+ );
+ const wstETHByStETHOnL2 = useWstETHByStETHOnL2(
+ isDappActiveOnL2 ? oneSteth : undefined,
+ );
const {
data: oneWstethConverted,
- initialLoading: isOneWstethConvertedLoading,
- } = useWstethBySteth(oneSteth);
+ initialLoading: oneWstethConvertedLoading,
+ } = isDappActiveOnL2 ? wstETHByStETHOnL2 : wstethBySteth;
const approveGasLimit = useApproveGasLimit();
const {
@@ -59,26 +73,38 @@ export const WrapFormStats = () => {
trimEllipsis
/>
-
-
-
+ {(!isDappActive || isShowAllowance) && (
+
+ {isWalletConnected && !isDappActiveAndNetworksMatched ? (
+ '-'
+ ) : (
+
+ )}
+
+ )}
-
+ {isWalletConnected && !isDappActiveAndNetworksMatched ? (
+ '-'
+ ) : (
+
+ )}
- {oneWstethConverted ? (
+ {isWalletConnected && !isDappActiveAndNetworksMatched ? (
+ '-'
+ ) : oneWstethConverted ? (
<>
1 {isSteth ? 'stETH' : 'ETH'} ={' '}
{
DATA_UNAVAILABLE
)}
-
+ {(!isDappActive || isShowAllowance) && (
+
+ )}
);
};
diff --git a/global.d.ts b/global.d.ts
index 2bafdd87b..5b3da69ef 100644
--- a/global.d.ts
+++ b/global.d.ts
@@ -28,6 +28,7 @@ declare module 'next/config' {
rpcUrls_1: string | undefined;
rpcUrls_17000: string | undefined;
rpcUrls_11155111: string | undefined;
+ rpcUrls_10: string | undefined;
rpcUrls_11155420: string | undefined;
cspTrustedHosts: string | undefined;
diff --git a/package.json b/package.json
index 3b99bc298..2364211e9 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
"@lidofinance/api-rpc": "^0.45.1",
"@lidofinance/eth-api-providers": "^0.45.1",
"@lidofinance/eth-providers": "^0.45.1",
- "@lidofinance/lido-ethereum-sdk": "3.5.0-alpha.1",
+ "@lidofinance/lido-ethereum-sdk": "3.5.0-alpha.3",
"@lidofinance/lido-ui": "^3.26.0",
"@lidofinance/next-api-wrapper": "^0.45.1",
"@lidofinance/next-ip-rate-limit": "^0.45.1",
diff --git a/pages/api/rpc.ts b/pages/api/rpc.ts
index 205aa9823..0efbf3218 100644
--- a/pages/api/rpc.ts
+++ b/pages/api/rpc.ts
@@ -54,6 +54,7 @@ const rpc = rpcFactory({
[CHAINS.Mainnet]: secretConfig.rpcUrls_1,
[CHAINS.Holesky]: secretConfig.rpcUrls_17000,
[CHAINS.Sepolia]: secretConfig.rpcUrls_11155111,
+ [CHAINS.Optimism]: secretConfig.rpcUrls_10,
[CHAINS.OptimismSepolia]: secretConfig.rpcUrls_11155420,
},
allowedRPCMethods: [
@@ -64,6 +65,7 @@ const rpc = rpcFactory({
'eth_estimateGas',
'eth_getBlockByNumber',
'eth_feeHistory',
+ 'eth_maxPriorityFeePerGas',
'eth_getBalance',
'eth_blockNumber',
'eth_getTransactionByHash',
diff --git a/providers/dapp-chain.tsx b/providers/dapp-chain.tsx
new file mode 100644
index 000000000..1a7dc3a5e
--- /dev/null
+++ b/providers/dapp-chain.tsx
@@ -0,0 +1,67 @@
+import React, { createContext, useContext, useState, useCallback } from 'react';
+import invariant from 'tiny-invariant';
+
+import { CHAINS } from 'consts/chains';
+
+export const ETHEREUM = 'ETHEREUM';
+export const OPTIMISM = 'OPTIMISM';
+export const UNKNOWN = 'UNKNOWN';
+
+export type ChainNameType = 'ETHEREUM' | 'OPTIMISM';
+type ChainNameOrUnknownType = ChainNameType | 'UNKNOWN';
+
+const getChainMainnetNameByChainId = (
+ chainId: number,
+): ChainNameOrUnknownType => {
+ if ([CHAINS.Mainnet, CHAINS.Holesky, CHAINS.Sepolia].includes(chainId)) {
+ return ETHEREUM;
+ } else if ([CHAINS.Optimism, CHAINS.OptimismSepolia].includes(chainId)) {
+ return OPTIMISM;
+ } else {
+ return UNKNOWN;
+ }
+};
+
+interface ContextValue {
+ chainName: string;
+ setChainName: React.Dispatch>;
+ getChainMainnetNameByChainId: (chainId: number) => ChainNameOrUnknownType;
+ isMatchDappChainAndWalletChain: (
+ walletChainId?: number | undefined,
+ ) => boolean;
+}
+
+const DappChainContext = createContext(undefined);
+
+export const useDappChain = () => {
+ const context = useContext(DappChainContext);
+ invariant(context, 'useDappChain was used outside of DappChainProvider');
+ return context;
+};
+
+export const DappChainProvider: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
+ const [chainName, setChainName] = useState(ETHEREUM);
+
+ const isMatchDappChainAndWalletChain = useCallback(
+ (walletChainId?: number | undefined): boolean => {
+ if (!walletChainId) return false;
+ return chainName === getChainMainnetNameByChainId(walletChainId);
+ },
+ [chainName],
+ );
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/providers/index.tsx b/providers/index.tsx
index 58407a7c9..5a75f2fc9 100644
--- a/providers/index.tsx
+++ b/providers/index.tsx
@@ -4,6 +4,7 @@ import { CookieThemeProvider } from '@lidofinance/lido-ui';
import { GlobalStyle } from 'styles';
import { ConfigProvider } from 'config';
+import { DappChainProvider } from './dapp-chain';
import { AppFlagProvider } from './app-flag';
import { IPFSInfoBoxStatusesProvider } from './ipfs-info-box-statuses';
import { InpageNavigationProvider } from './inpage-navigation';
@@ -21,18 +22,20 @@ export const Providers: FC> = ({
}) => (
-
-
-
-
-
-
- {children}
-
-
-
-
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
);
diff --git a/providers/lido-sdk.tsx b/providers/lido-sdk.tsx
index 62435d3fb..54bc8f218 100644
--- a/providers/lido-sdk.tsx
+++ b/providers/lido-sdk.tsx
@@ -1,24 +1,25 @@
import { createContext, useContext, useMemo } from 'react';
-import { LidoSDKCore } from '@lidofinance/lido-ethereum-sdk/core';
+import invariant from 'tiny-invariant';
+import { useChainId, usePublicClient, useWalletClient } from 'wagmi';
+
+import { CHAINS, LidoSDKCore } from '@lidofinance/lido-ethereum-sdk/core';
import {
LidoSDKstETH,
LidoSDKwstETH,
} from '@lidofinance/lido-ethereum-sdk/erc20';
-import {
- LidoSDKL2Steth,
- LidoSDKL2Wsteth,
-} from '@lidofinance/lido-ethereum-sdk/l2';
-import invariant from 'tiny-invariant';
-import { useChainId, useClient, useConnectorClient } from 'wagmi';
+import { LidoSDKL2 } from '@lidofinance/lido-ethereum-sdk/l2';
+import { LidoSDKWrap } from '@lidofinance/lido-ethereum-sdk/wrap';
+
import { useTokenTransferSubscription } from 'shared/hooks/use-balance';
-import { useGetRpcUrlByChainId } from 'config/rpc';
+import { LIDO_L2_CONTRACT_ADDRESSES } from '@lidofinance/lido-ethereum-sdk/common';
type LidoSDKContextValue = {
core: LidoSDKCore;
- steth: LidoSDKstETH;
- wsteth: LidoSDKwstETH;
- l2Steth: LidoSDKL2Steth;
- l2Wsteth: LidoSDKL2Wsteth;
+ stETH: LidoSDKstETH;
+ wstETH: LidoSDKwstETH;
+ l2: LidoSDKL2;
+ wrap: LidoSDKWrap;
+ isL2: boolean;
subscribeToTokenUpdates: ReturnType;
};
@@ -33,38 +34,37 @@ export const useLidoSDK = () => {
export const LidoSDKProvider = ({ children }: React.PropsWithChildren) => {
const subscribe = useTokenTransferSubscription();
- const publicClient = useClient();
+ const publicClient = usePublicClient();
const chainId = useChainId();
- const getRpcUrl = useGetRpcUrlByChainId();
- const fallbackRpcUrl = !publicClient ? getRpcUrl(chainId) : undefined;
- const { data: walletClient } = useConnectorClient();
+ const { data: walletClient } = useWalletClient();
- const sdk = useMemo(() => {
+ const contextValue = useMemo(() => {
+ // @ts-expect-error: typing (viem + LidoSDK)
const core = new LidoSDKCore({
chainId,
logMode: 'none',
- rpcProvider: publicClient as any,
- web3Provider: walletClient as any,
- // viem client can be unavailable on ipfs+dev first renders
- rpcUrls: !publicClient && fallbackRpcUrl ? [fallbackRpcUrl] : undefined,
+ rpcProvider: publicClient,
+ web3Provider: walletClient,
});
- const steth = new LidoSDKstETH({ core });
- const wsteth = new LidoSDKwstETH({ core });
-
- const l2Steth = new LidoSDKL2Steth({ core });
- const l2Wsteth = new LidoSDKL2Wsteth({ core });
+ const stETH = new LidoSDKstETH({ core });
+ const wstETH = new LidoSDKwstETH({ core });
+ const wrap = new LidoSDKWrap({ core });
+ const l2 = new LidoSDKL2({ core });
return {
core,
- steth,
- wsteth,
- l2Steth,
- l2Wsteth,
+ stETH,
+ wstETH,
+ wrap,
+ l2,
+ isL2: !!LIDO_L2_CONTRACT_ADDRESSES[chainId as CHAINS],
subscribeToTokenUpdates: subscribe,
};
- }, [chainId, fallbackRpcUrl, publicClient, subscribe, walletClient]);
+ }, [chainId, publicClient, subscribe, walletClient]);
return (
- {children}
+
+ {children}
+
);
};
diff --git a/providers/rewardsHistory.tsx b/providers/rewardsHistory.tsx
index fbf354ef9..5230c38d7 100644
--- a/providers/rewardsHistory.tsx
+++ b/providers/rewardsHistory.tsx
@@ -49,7 +49,8 @@ export const RewardsHistoryContext = createContext({} as RewardsHistoryValue);
const RewardsHistoryProvider: FC = (props) => {
const { children } = props;
- const { isWalletConnected, isSupportedChain } = useDappStatus();
+ const { isWalletConnected, isSupportedChain, isAccountActiveOnL2 } =
+ useDappStatus();
const [currency, setCurrency] = useState(DEFAULT_CURRENCY.id);
@@ -92,9 +93,9 @@ const RewardsHistoryProvider: FC = (props) => {
const isDataAvailable = useMemo(() => {
const isDataNotAvailable =
- !data || (isWalletConnected && !isSupportedChain);
+ !data || (isWalletConnected && !isSupportedChain) || isAccountActiveOnL2;
return !isDataNotAvailable;
- }, [data, isWalletConnected, isSupportedChain]);
+ }, [data, isWalletConnected, isSupportedChain, isAccountActiveOnL2]);
const value = useMemo(
(): RewardsHistoryValue => ({
diff --git a/providers/sdk-legacy.tsx b/providers/sdk-legacy.tsx
index 29847e1b0..64db1ef99 100644
--- a/providers/sdk-legacy.tsx
+++ b/providers/sdk-legacy.tsx
@@ -24,7 +24,7 @@ export const SDKLegacyProvider = ({
}: SDKLegacyProviderProps) => {
const { chainId: wagmiChainId = defaultChainId, address } = useAccount();
const { supportedChains } = useSupportedChains();
- const { isDappActive } = useDappStatus();
+ const { isDappActiveOnL1 } = useDappStatus();
const config = useConfig();
const client = useClient();
const { rpc } = useReefKnotContext();
@@ -44,7 +44,8 @@ export const SDKLegacyProvider = ({
};
const getProviderValue = async () => {
- if (!client || !address || !isDappActive) return undefined;
+ // old sdk can only supports wallet connection on L1
+ if (!client || !address || !isDappActiveOnL1) return undefined;
const { chain } = client;
const providerTransport = await getProviderTransport();
@@ -69,7 +70,14 @@ export const SDKLegacyProvider = ({
return () => {
isHookMounted = false;
};
- }, [config, config.state, client, address, isDappActive, pollingInterval]);
+ }, [
+ config,
+ config.state,
+ client,
+ address,
+ isDappActiveOnL1,
+ pollingInterval,
+ ]);
const supportedChainIds = useMemo(
() => supportedChains.map((chain) => chain.chainId),
@@ -77,11 +85,14 @@ export const SDKLegacyProvider = ({
);
const chainId = useMemo(() => {
- return supportedChainIds.indexOf(wagmiChainId) > -1 &&
- SDK_LEGACY_SUPPORTED_CHAINS.indexOf(wagmiChainId) > -1
- ? wagmiChainId
- : defaultChainId;
- }, [defaultChainId, supportedChainIds, wagmiChainId]);
+ if (providerWeb3) {
+ return supportedChainIds.indexOf(wagmiChainId) > -1 &&
+ SDK_LEGACY_SUPPORTED_CHAINS.indexOf(wagmiChainId) > -1
+ ? wagmiChainId
+ : defaultChainId;
+ }
+ return defaultChainId;
+ }, [defaultChainId, providerWeb3, supportedChainIds, wagmiChainId]);
const providerRpc = useMemo(
() => getStaticRpcBatchProvider(chainId, rpc[chainId], 0, pollingInterval),
diff --git a/providers/web3.tsx b/providers/web3.tsx
index e68442d82..43e787c80 100644
--- a/providers/web3.tsx
+++ b/providers/web3.tsx
@@ -20,7 +20,13 @@ import { useWeb3Transport } from 'utils/use-web3-transport';
type ChainsList = [wagmiChains.Chain, ...wagmiChains.Chain[]];
-const wagmiChainsArray = Object.values(wagmiChains) as any as ChainsList;
+const wagmiChainMap = Object.values(wagmiChains).reduce(
+ (acc, chain) => {
+ acc[chain.id] = chain;
+ return acc;
+ },
+ {} as Record,
+);
const queryClient = new QueryClient({
defaultOptions: {
@@ -39,15 +45,14 @@ const Web3Provider: FC = ({ children }) => {
} = useUserConfig();
const { supportedChains, defaultChain } = useMemo(() => {
- const supportedChains = wagmiChainsArray.filter((chain) =>
- supportedChainIds.includes(chain.id),
- );
+ // must preserve order of supportedChainIds
+ const supportedChains = supportedChainIds
+ .map((id) => wagmiChainMap[id])
+ .filter((chain) => chain) as ChainsList;
- const defaultChain =
- supportedChains.find((chain) => chain.id === defaultChainId) ||
- supportedChains[0]; // first supported chain as fallback
+ const defaultChain = wagmiChainMap[defaultChainId] || supportedChains[0];
return {
- supportedChains: supportedChains as ChainsList,
+ supportedChains,
defaultChain,
};
}, [defaultChainId, supportedChainIds]);
diff --git a/scripts/log-environment-variables.mjs b/scripts/log-environment-variables.mjs
index fada43456..2aed3ef33 100644
--- a/scripts/log-environment-variables.mjs
+++ b/scripts/log-environment-variables.mjs
@@ -31,6 +31,7 @@ export const secretKeys = [
'EL_RPC_URLS_5',
'EL_RPC_URLS_17000',
'EL_RPC_URLS_11155111',
+ 'EL_RPC_URLS_10',
'EL_RPC_URLS_11155420',
]
diff --git a/shared/components/data-table-row-steth-by-wsteth/data-table-row-steth-by-wsteth.tsx b/shared/components/data-table-row-steth-by-wsteth/data-table-row-steth-by-wsteth.tsx
index 7d4373ffc..cfaf78357 100644
--- a/shared/components/data-table-row-steth-by-wsteth/data-table-row-steth-by-wsteth.tsx
+++ b/shared/components/data-table-row-steth-by-wsteth/data-table-row-steth-by-wsteth.tsx
@@ -1,10 +1,11 @@
import { DataTableRow } from '@lidofinance/lido-ui';
import { parseEther } from '@ethersproject/units';
+import { DATA_UNAVAILABLE } from 'consts/text';
import { FormatToken } from 'shared/formatters';
import { useStethByWsteth } from 'shared/hooks';
-
-import { DATA_UNAVAILABLE } from 'consts/text';
+import { useStETHByWstETHOnL2 } from 'shared/hooks/use-stETH-by-wstETH-on-l2';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
const OneWsteth = parseEther('1');
@@ -15,7 +16,21 @@ type DataTableRowStethByWstethProps = {
export const DataTableRowStethByWsteth = ({
toSymbol = 'stETH',
}: DataTableRowStethByWstethProps) => {
- const { data, initialLoading } = useStethByWsteth(OneWsteth);
+ const {
+ isWalletConnected,
+ isDappActiveOnL2,
+ isDappActiveAndNetworksMatched,
+ } = useDappStatus();
+ const stethByWsteth = useStethByWsteth(
+ !isDappActiveOnL2 ? OneWsteth : undefined,
+ );
+ const stETHByWstETHOnL2 = useStETHByWstETHOnL2(
+ isDappActiveOnL2 ? OneWsteth : undefined,
+ );
+
+ const { data, initialLoading } = isDappActiveOnL2
+ ? stETHByWstETHOnL2
+ : stethByWsteth;
return (
- {data ? (
+ {isWalletConnected && !isDappActiveAndNetworksMatched ? (
+ '-'
+ ) : data ? (
<>
1 wstETH =
span {
+ border-radius: 10px !important;
+ }
+ }
+
& > span {
${({ theme, disabled }) =>
theme.name === 'dark'
diff --git a/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx b/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx
new file mode 100644
index 000000000..c73dd4f3b
--- /dev/null
+++ b/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx
@@ -0,0 +1,102 @@
+import {
+ FC,
+ ReactNode,
+ useState,
+ useCallback,
+ useEffect,
+ useMemo,
+} from 'react';
+import { useRouter } from 'next/router';
+import { useAccount } from 'wagmi';
+
+import { Option } from '@lidofinance/lido-ui';
+
+import { ReactComponent as OptimismLogo } from 'assets/icons/chain-toggler/optimism.svg';
+import { ReactComponent as EthereumMainnetLogo } from 'assets/icons/chain-toggler/mainnet.svg';
+
+import { CHAINS } from 'consts/chains';
+import {
+ ChainNameType,
+ ETHEREUM,
+ OPTIMISM,
+ useDappChain,
+} from 'providers/dapp-chain';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+
+import { SelectIconTooltip } from './components/select-icon-tooltip/select-icon-tooltip';
+import { SelectIconStyled, SelectIconWrapper } from './styles';
+
+const iconsMap: Record = {
+ [ETHEREUM]: ,
+ [OPTIMISM]: ,
+};
+
+export const ChainSwitcher: FC = () => {
+ const { chainId } = useAccount();
+ const { setChainName } = useDappChain();
+ const { isDappActiveAndNetworksMatched } = useDappStatus();
+ const router = useRouter();
+
+ const [value, setValue] = useState(ETHEREUM);
+
+ const isOnWrapUnwrapPage = useMemo(
+ () => router.pathname === '/wrap/[[...mode]]',
+ [router.pathname],
+ );
+
+ useEffect(() => {
+ if (!chainId) return;
+
+ if ([CHAINS.Mainnet, CHAINS.Holesky, CHAINS.Sepolia].includes(chainId)) {
+ setValue(ETHEREUM);
+ setChainName(ETHEREUM);
+ } else if (
+ [CHAINS.Optimism, CHAINS.OptimismSepolia].includes(chainId) &&
+ isOnWrapUnwrapPage
+ ) {
+ setValue(OPTIMISM);
+ setChainName(OPTIMISM);
+ }
+ }, [chainId, isOnWrapUnwrapPage, setChainName]);
+
+ useEffect(() => {
+ if (!isOnWrapUnwrapPage) {
+ setValue(ETHEREUM);
+ setChainName(ETHEREUM);
+ }
+ }, [isOnWrapUnwrapPage, setChainName]);
+
+ const onChange = useCallback(
+ (value: any) => {
+ setValue(value as ChainNameType);
+ setChainName(value as ChainNameType);
+ },
+ [setChainName],
+ );
+
+ return (
+
+
+
+ Ethereum
+
+
+ Optimism
+
+
+ {isOnWrapUnwrapPage && !isDappActiveAndNetworksMatched && (
+
+ {isOnWrapUnwrapPage
+ ? 'This network doesn’t match your wallet’s network'
+ : 'Don’t forget to switch to Ethereum'}
+
+ )}
+
+ );
+};
diff --git a/shared/components/layout/header/components/chain-switcher/components/select-icon-tooltip/select-icon-tooltip.tsx b/shared/components/layout/header/components/chain-switcher/components/select-icon-tooltip/select-icon-tooltip.tsx
new file mode 100644
index 000000000..ca5db3a5f
--- /dev/null
+++ b/shared/components/layout/header/components/chain-switcher/components/select-icon-tooltip/select-icon-tooltip.tsx
@@ -0,0 +1,24 @@
+import { FC, PropsWithChildren } from 'react';
+import { ThemeProvider, themeDark, Text } from '@lidofinance/lido-ui';
+
+import { SelectIconTooltipWrapper, SelectIconTooltipContent } from './styles';
+
+type SelectIconTooltipProps = {
+ showArrow: boolean;
+};
+
+export const SelectIconTooltip: FC<
+ PropsWithChildren
+> = ({ children, showArrow }) => {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+};
diff --git a/shared/components/layout/header/components/chain-switcher/components/select-icon-tooltip/styles.tsx b/shared/components/layout/header/components/chain-switcher/components/select-icon-tooltip/styles.tsx
new file mode 100644
index 000000000..11080a9af
--- /dev/null
+++ b/shared/components/layout/header/components/chain-switcher/components/select-icon-tooltip/styles.tsx
@@ -0,0 +1,42 @@
+import styled, { css } from 'styled-components';
+
+interface TooltipProps {
+ $showArrow?: boolean;
+}
+
+export const SelectIconTooltipWrapper = styled.div`
+ position: absolute;
+ left: 0;
+ top: calc(100% + 16px);
+ width: 244px;
+ z-index: 5;
+`;
+
+export const SelectIconTooltipContent = styled.div`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: ${({ theme }) => theme.spaceMap.md}px
+ ${({ theme }) => theme.spaceMap.md}px;
+ background-color: var(--lido-color-accent);
+ border-radius: ${({ theme }) => theme.borderRadiusesMap.sm}px;
+
+ ${({ $showArrow }) =>
+ $showArrow &&
+ css`
+ &:after {
+ content: '';
+ position: absolute;
+ top: -6px;
+ left: 27px;
+ display: block;
+ width: 12px;
+ height: 12px;
+ transform: rotate(45deg);
+ flex-shrink: 0;
+ border-radius: 2px 0 0 0;
+ background: var(--lido-color-accent);
+ }
+ `}
+`;
diff --git a/shared/components/layout/header/components/chain-switcher/styles.tsx b/shared/components/layout/header/components/chain-switcher/styles.tsx
new file mode 100644
index 000000000..4727d1278
--- /dev/null
+++ b/shared/components/layout/header/components/chain-switcher/styles.tsx
@@ -0,0 +1,34 @@
+import styled, { css } from 'styled-components';
+import { SelectIcon } from '@lidofinance/lido-ui';
+
+export const SelectIconWrapper = styled.div`
+ position: relative;
+`;
+
+export const SelectIconStyled = styled(SelectIcon)`
+ overflow: ${({ disabled }) => (disabled ? 'hidden' : 'visible')};
+ width: ${({ disabled }) => (disabled ? '44px' : '68px')};
+ height: 44px;
+
+ &:not(:disabled):hover {
+ & > span {
+ ${({ theme, disabled }) =>
+ theme.name === 'dark'
+ ? css`
+ background: ${!disabled && '#34343D'};
+ `
+ : css`
+ background: ${!disabled && '#000A3D08'};
+ `}
+ }
+ }
+
+ & > span {
+ border: 0;
+ padding-left: 8px;
+ padding-right: 8px;
+ }
+
+ border-radius: 10px;
+ margin-right: 12px;
+`;
diff --git a/shared/components/layout/header/components/header-wallet.tsx b/shared/components/layout/header/components/header-wallet.tsx
index f7c5a0f45..53798a2ff 100644
--- a/shared/components/layout/header/components/header-wallet.tsx
+++ b/shared/components/layout/header/components/header-wallet.tsx
@@ -2,11 +2,11 @@ import { FC, useMemo } from 'react';
import { useRouter } from 'next/router';
import { useAccount } from 'wagmi';
-import { CHAINS, getChainColor } from '@lido-sdk/constants';
-import { ThemeToggler } from '@lidofinance/lido-ui';
+import { CHAINS as legacySDKCHAINS, getChainColor } from '@lido-sdk/constants';
import { config } from 'config';
import { useUserConfig } from 'config/user-config';
+import { CHAINS } from 'consts/chains';
import { IPFSInfoBox } from 'features/ipfs/ipfs-info-box';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
@@ -19,6 +19,8 @@ import {
DotStyle,
IPFSInfoBoxOnlyDesktopWrapper,
} from '../styles';
+import { ThemeTogglerStyled } from './styles';
+import { ChainSwitcher } from './chain-switcher/chain-switcher';
const HeaderWallet: FC = () => {
const router = useRouter();
@@ -26,8 +28,12 @@ const HeaderWallet: FC = () => {
const { defaultChain: defaultChainId } = useUserConfig();
const { isDappActive } = useDappStatus();
- const chainName = CHAINS[chainId || defaultChainId];
- const testNet = chainId !== CHAINS.Mainnet;
+ let chainName = legacySDKCHAINS[chainId || defaultChainId];
+ if (!chainName && chainId === CHAINS.OptimismSepolia) {
+ chainName = 'Optimism Sepolia';
+ }
+
+ const testNet = chainId !== legacySDKCHAINS.Mainnet;
const showNet = testNet && isDappActive;
const queryTheme = router?.query?.theme;
@@ -50,12 +56,15 @@ const HeaderWallet: FC = () => {
>
)}
{address ? (
-
+ <>
+
+
+ >
) : (
)}
{config.ipfsMode && }
- {!queryTheme && }
+ {!queryTheme && }
{config.ipfsMode && (
diff --git a/shared/components/layout/header/components/styles.tsx b/shared/components/layout/header/components/styles.tsx
new file mode 100644
index 000000000..06a483884
--- /dev/null
+++ b/shared/components/layout/header/components/styles.tsx
@@ -0,0 +1,7 @@
+import styled from 'styled-components';
+import { ThemeToggler } from '@lidofinance/lido-ui';
+
+export const ThemeTogglerStyled = styled(ThemeToggler)`
+ border-radius: 10px;
+ margin-left: 12px;
+`;
diff --git a/shared/components/layout/header/styles.tsx b/shared/components/layout/header/styles.tsx
index ea4ff9ad4..4d30f051c 100644
--- a/shared/components/layout/header/styles.tsx
+++ b/shared/components/layout/header/styles.tsx
@@ -22,6 +22,13 @@ export const HeaderWalletChainStyle = styled.span<{ $color: string }>`
margin-right: ${({ theme }) => theme.spaceMap.sm}px;
color: ${({ $color }) => $color};
line-height: 1.2em;
+
+ ${({ theme }) => theme.mediaQueries.md} {
+ font-size: 9px;
+ // for very long network names
+ text-overflow: ellipsis;
+ max-width: 38px;
+ }
`;
const glimmer = keyframes`
diff --git a/shared/components/token-to-wallet/token-to-wallet.tsx b/shared/components/token-to-wallet/token-to-wallet.tsx
index b1bbf4857..bad2f4d24 100644
--- a/shared/components/token-to-wallet/token-to-wallet.tsx
+++ b/shared/components/token-to-wallet/token-to-wallet.tsx
@@ -1,22 +1,62 @@
import { ToastInfo, Tooltip } from '@lidofinance/lido-ui';
-import { useTokenToWallet } from '@lido-sdk/react';
import { TokenToWalletStyle } from './styles';
import { Component } from 'types';
+import { useWalletClient, useWatchAsset } from 'wagmi';
+import { Address, getContract } from 'viem';
+import { useConnectorInfo } from 'reef-knot/core-react';
-export type TokenToWalletComponent = Component<'button', { address: string }>;
+export type TokenToWalletComponent = Component<'button', { address?: string }>;
-export const TokenToWallet: TokenToWalletComponent = (props) => {
- const { address, ...rest } = props;
- const { addToken } = useTokenToWallet(address);
+const ERC20_METADATA_ABI = [
+ {
+ inputs: [],
+ name: 'symbol',
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'decimals',
+ outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+] as const;
- if (!addToken) return null;
+export const TokenToWallet: TokenToWalletComponent = ({ address, ...rest }) => {
+ const { watchAssetAsync } = useWatchAsset();
+ const { isInjected } = useConnectorInfo();
+ const { data: walletClient } = useWalletClient();
+
+ if (!walletClient || !address || !isInjected) return null;
const onClickHandler = async () => {
- const result = await addToken();
- if (!result) return;
+ if (!address) return;
+
+ try {
+ const tokenContract = getContract({
+ abi: ERC20_METADATA_ABI,
+ address: address as Address,
+ client: walletClient,
+ });
+
+ const [decimals, symbol] = await Promise.all([
+ tokenContract.read.decimals(),
+ tokenContract.read.symbol(),
+ ]);
+
+ const result = await watchAssetAsync({
+ type: 'ERC20',
+ options: { address, decimals, symbol },
+ });
+ if (!result) return;
- ToastInfo('Tokens were successfully added to your wallet', {});
+ ToastInfo('Tokens were successfully added to your wallet', {});
+ } catch (error) {
+ console.warn('[TokenToWallet] error adding token to wallet', error);
+ }
};
return (
diff --git a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx
index a09cdccec..c1ab75a23 100644
--- a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx
+++ b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx
@@ -1,6 +1,8 @@
+import { useAccount } from 'wagmi';
import { Link } from '@lidofinance/lido-ui';
-import { useSDK } from '@lido-sdk/react';
-import { getEtherscanTxLink } from '@lido-sdk/helpers';
+
+import { CHAINS } from 'consts/chains';
+import { getEtherscanTxLink } from 'utils/get-etherscan-tx-link';
type TxLinkEtherscanProps = {
text?: string;
@@ -10,12 +12,15 @@ type TxLinkEtherscanProps = {
export const TxLinkEtherscan = (props: TxLinkEtherscanProps) => {
const { txHash, text = 'View on Etherscan', onClick } = props;
- const { chainId } = useSDK();
+ const { chainId } = useAccount();
if (!txHash) return null;
return (
-
+
{text}
);
diff --git a/shared/hook-form/controls/submit-button-hook-form.tsx b/shared/hook-form/controls/submit-button-hook-form.tsx
index baf4cf5d4..0b4c6600c 100644
--- a/shared/hook-form/controls/submit-button-hook-form.tsx
+++ b/shared/hook-form/controls/submit-button-hook-form.tsx
@@ -3,7 +3,8 @@ import { useAccount } from 'wagmi';
import { ButtonIcon, Lock } from '@lidofinance/lido-ui';
import { useIsSupportedChain } from 'shared/hooks/use-is-supported-chain';
-import { Connect, UnsupportedChainButton } from 'shared/wallet';
+import { useDappStatus } from 'shared/hooks/use-dapp-status';
+import { Connect, DisabledButton } from 'shared/wallet';
import { isValidationErrorTypeValidate } from '../validation/validation-error';
@@ -23,6 +24,7 @@ export const SubmitButtonHookForm: React.FC = ({
}) => {
const { isConnected } = useAccount();
const isSupportedChain = useIsSupportedChain();
+ const { isDappActiveAndNetworksMatched } = useDappStatus();
const { isValidating, isSubmitting } = useFormState();
const { errors } = useFormState>();
@@ -31,10 +33,9 @@ export const SubmitButtonHookForm: React.FC = ({
return ;
}
- if (!isSupportedChain) {
- return ;
+ if (!isSupportedChain || !isDappActiveAndNetworksMatched) {
+ return {props.children} ;
}
-
const disabled =
(errorField &&
!!errors[errorField] &&
@@ -46,8 +47,8 @@ export const SubmitButtonHookForm: React.FC = ({
fullwidth
type="submit"
loading={isValidating || isSubmitting}
- disabled={disabled}
icon={icon || isLocked ? : <>>}
+ disabled={disabled}
{...props}
/>
);
diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts
index bc624cfab..8ab813c00 100644
--- a/shared/hooks/index.ts
+++ b/shared/hooks/index.ts
@@ -10,4 +10,3 @@ export * from './useStakingLimitInfo';
export * from './useMatomoEventHandle';
export * from './useERC20PermitSignature';
export * from './useDebouncedValue';
-export * from './useIsContract';
diff --git a/shared/hooks/use-allowance.ts b/shared/hooks/use-allowance.ts
index 7143700c2..a62d76414 100644
--- a/shared/hooks/use-allowance.ts
+++ b/shared/hooks/use-allowance.ts
@@ -57,9 +57,9 @@ type OnLogsFn = WatchContractEventOnLogsFn<
>;
type UseAllowanceProps = {
- token: Address;
- account: Address;
- spender: Address;
+ token?: Address;
+ account?: Address;
+ spender?: Address;
};
const onError = (error: unknown) =>
@@ -77,8 +77,11 @@ export const useAllowance = ({
abi: Erc20AllowanceAbi,
address: token,
functionName: 'allowance',
- args: [account, spender],
- query: { enabled, select: nativeToBN },
+ args: [account, spender] as [Address, Address],
+ query: {
+ enabled,
+ select: nativeToBN,
+ },
});
const onLogs: OnLogsFn = useCallback(
diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts
index 3df1fb537..e29d6ee3d 100644
--- a/shared/hooks/use-balance.ts
+++ b/shared/hooks/use-balance.ts
@@ -17,7 +17,6 @@ import {
import type { GetBalanceData } from 'wagmi/query';
import { config } from 'config';
-import { isSDKSupportedL2Chain, CHAINS } from 'consts/chains';
const nativeToBN = (data: bigint) => BigNumber.from(data.toString());
@@ -252,20 +251,17 @@ export const useStethBalance = ({
account,
shouldSubscribeToUpdates = true,
}: UseBalanceProps = {}) => {
+ const { core, l2, stETH, isL2 } = useLidoSDK();
const { address } = useAccount();
- const mergedAccount = account ?? address;
- const { steth, l2Steth, core } = useLidoSDK();
+ const mergedAccount = account ?? address;
const { data: contract, isLoading } = useQuery({
- queryKey: ['steth-contract', core.chainId],
+ queryKey: ['steth-contract', core.chainId, isL2],
enabled: !!mergedAccount,
staleTime: Infinity,
- queryFn: async () =>
- isSDKSupportedL2Chain(core.chainId as CHAINS)
- ? l2Steth.getContract()
- : steth.getContract(),
+ queryFn: async () => (isL2 ? l2.steth.getContract() : stETH.getContract()),
});
const balanceData = useTokenBalance(
@@ -274,7 +270,11 @@ export const useStethBalance = ({
shouldSubscribeToUpdates,
);
- return { ...balanceData, isLoading: isLoading || balanceData.isLoading };
+ return {
+ ...balanceData,
+ tokenAddress: contract ? contract.address : undefined,
+ isLoading: isLoading || balanceData.isLoading,
+ };
};
export const useWstethBalance = ({
@@ -283,17 +283,19 @@ export const useWstethBalance = ({
}: UseBalanceProps = {}) => {
const { address } = useAccount();
const mergedAccount = account ?? address;
-
- const { wsteth, l2Wsteth, core } = useLidoSDK();
+ const {
+ core: lidoSDKCore,
+ l2: lidoSDKL2,
+ wstETH: lidoSDKwstETH,
+ isL2,
+ } = useLidoSDK();
const { data: contract, isLoading } = useQuery({
- queryKey: ['wsteth-contract', core.chainId],
+ queryKey: ['wsteth-contract', lidoSDKCore.chainId, isL2],
enabled: !!mergedAccount,
staleTime: Infinity,
queryFn: async () =>
- isSDKSupportedL2Chain(core.chainId as CHAINS)
- ? l2Wsteth.getContract()
- : wsteth.getContract(),
+ isL2 ? lidoSDKL2.wsteth.getContract() : lidoSDKwstETH.getContract(),
});
const balanceData = useTokenBalance(
@@ -302,5 +304,9 @@ export const useWstethBalance = ({
shouldSubscribeToUpdates,
);
- return { ...balanceData, isLoading: isLoading || balanceData.isLoading };
+ return {
+ ...balanceData,
+ tokenAddress: contract ? contract.address : undefined,
+ isLoading: isLoading || balanceData.isLoading,
+ };
};
diff --git a/shared/hooks/use-dapp-status.ts b/shared/hooks/use-dapp-status.ts
index 7d6517136..b39f63872 100644
--- a/shared/hooks/use-dapp-status.ts
+++ b/shared/hooks/use-dapp-status.ts
@@ -5,11 +5,13 @@ import { isSDKSupportedL2Chain, LIDO_MULTICHAIN_CHAINS } from 'consts/chains';
import { useIsSupportedChain } from './use-is-supported-chain';
import { useConfig } from 'config';
+import { useDappChain } from 'providers/dapp-chain';
export const useDappStatus = () => {
const { multiChainBanner } = useConfig().externalConfig;
const { chainId, isConnected: isWalletConnected } = useAccount();
const isSupportedChain = useIsSupportedChain();
+ const { isMatchDappChainAndWalletChain } = useDappChain();
const isLidoMultichainChain = useMemo(
() =>
@@ -19,24 +21,54 @@ export const useDappStatus = () => {
[chainId, multiChainBanner],
);
- // TODO: rename isDappActive to isDappActiveOnL1
- const isDappActive = useMemo(() => {
- if (!chainId) return false;
+ const dappStatuses = useMemo(() => {
+ const isDappActive = chainId
+ ? isWalletConnected && isSupportedChain
+ : false;
- return isWalletConnected && isSupportedChain;
- }, [chainId, isWalletConnected, isSupportedChain]);
+ const isAccountActiveOnL1 = chainId
+ ? isDappActive && !isSDKSupportedL2Chain(chainId)
+ : false;
+ const isAccountActiveOnL2 = chainId
+ ? isDappActive && isSDKSupportedL2Chain(chainId)
+ : false;
- const isDappActiveOnL2 = useMemo(() => {
- if (!chainId) return false;
+ const isDappActiveOnL1 = chainId
+ ? isAccountActiveOnL1 && isMatchDappChainAndWalletChain(chainId)
+ : false;
+ const isDappActiveOnL2 = chainId
+ ? isAccountActiveOnL2 && isMatchDappChainAndWalletChain(chainId)
+ : false;
- return isSDKSupportedL2Chain(chainId);
- }, [chainId]);
+ const isDappActiveAndNetworksMatched = chainId
+ ? (isAccountActiveOnL1 && isDappActiveOnL1) ||
+ (isAccountActiveOnL2 && isDappActiveOnL2)
+ : false;
+
+ return {
+ // TODO: rename to isAccountActive
+ isDappActive,
+
+ isAccountActiveOnL1,
+ isAccountActiveOnL2,
+
+ isDappActiveOnL1,
+ isDappActiveOnL2,
+
+ // TODO: rename to isDappActive (see above)
+ isDappActiveAndNetworksMatched,
+ };
+ }, [
+ chainId,
+ isMatchDappChainAndWalletChain,
+ isSupportedChain,
+ isWalletConnected,
+ ]);
return {
isWalletConnected,
isSupportedChain,
isLidoMultichainChain,
- isDappActive,
- isDappActiveOnL2,
+ ...dappStatuses,
};
};
diff --git a/shared/hooks/use-is-contract.ts b/shared/hooks/use-is-contract.ts
new file mode 100644
index 000000000..b0c4c1eb3
--- /dev/null
+++ b/shared/hooks/use-is-contract.ts
@@ -0,0 +1,32 @@
+import { useCallback } from 'react';
+import type { Address, Hex } from 'viem';
+
+import { useBytecode, usePublicClient } from 'wagmi';
+
+// helper hook until migration to wagmi is complete
+export const useGetIsContract = () => {
+ const client = usePublicClient();
+ return useCallback(
+ async (address: Address) => {
+ const code = await client?.getCode({ address });
+
+ return toBool(code);
+ },
+ [client],
+ );
+};
+
+const toBool = (data: Hex | undefined) => {
+ return !!(data && data != '0x');
+};
+
+// helper hook until migration to wagmi is complete
+export const useIsContract = (account?: string | null) => {
+ return useBytecode({
+ address: account as Address,
+ query: {
+ enabled: !!account,
+ select: toBool,
+ },
+ });
+};
diff --git a/shared/hooks/use-lido-multichain-fallback-condition.ts b/shared/hooks/use-lido-multichain-fallback-condition.ts
index ba20e9b35..040b358ad 100644
--- a/shared/hooks/use-lido-multichain-fallback-condition.ts
+++ b/shared/hooks/use-lido-multichain-fallback-condition.ts
@@ -1,21 +1,8 @@
-import { useConfig } from 'config';
import { useDappStatus } from 'shared/hooks/use-dapp-status';
-import { CHAINS } from 'consts/chains';
-import { overrideWithQAMockBoolean } from 'utils/qa';
export const useLidoMultichainFallbackCondition = () => {
- const { config } = useConfig();
const { isLidoMultichainChain } = useDappStatus();
-
- // Display Lido Multichain banners only if defaultChain=Mainnet
- // Or via QA helpers override
- const isLidoMultichainFallbackEnabled = overrideWithQAMockBoolean(
- config.defaultChain === CHAINS.Mainnet,
- 'mock-qa-helpers-show-lido-multichain-banners-on-testnet',
- );
-
- const showLidoMultichainFallback =
- isLidoMultichainChain && isLidoMultichainFallbackEnabled;
+ const showLidoMultichainFallback = isLidoMultichainChain;
return {
showLidoMultichainFallback,
diff --git a/shared/hooks/use-stETH-by-wstETH-on-l2.ts b/shared/hooks/use-stETH-by-wstETH-on-l2.ts
new file mode 100644
index 000000000..2901fea58
--- /dev/null
+++ b/shared/hooks/use-stETH-by-wstETH-on-l2.ts
@@ -0,0 +1,27 @@
+import { BigNumber } from 'ethers';
+import useSWR from 'swr';
+
+import { useLidoSDK } from 'providers/lido-sdk';
+
+export const useStETHByWstETHOnL2 = (wsteth: BigNumber | undefined) => {
+ const { l2 } = useLidoSDK();
+
+ const { data, error, isValidating, mutate } = useSWR(
+ // if key is null, SWR will not fetch any data.
+ wsteth
+ ? ['[swr:use-steth-by-wsteth-l2]', wsteth.toBigInt(), l2.core.chainId]
+ : null,
+ (_key: string, amount: bigint) => {
+ return l2.steth.convertToSteth(amount);
+ },
+ );
+
+ return {
+ // Make usable for UnwrapStats component (in future can be used like bigint)
+ data: typeof data !== 'undefined' ? BigNumber.from(data) : undefined,
+ initialLoading: isValidating && !data && !error,
+ loading: isValidating,
+ error,
+ update: mutate,
+ };
+};
diff --git a/shared/hooks/use-staking-limit-warning.ts b/shared/hooks/use-staking-limit-warning.ts
index f0f8d59b9..30696a904 100644
--- a/shared/hooks/use-staking-limit-warning.ts
+++ b/shared/hooks/use-staking-limit-warning.ts
@@ -2,14 +2,14 @@ import { useDappStatus } from 'shared/hooks/use-dapp-status';
import { LIMIT_LEVEL } from 'types';
export const useStakingLimitWarning = (stakingLimitLevel?: LIMIT_LEVEL) => {
- const { isDappActive } = useDappStatus();
+ const { isDappActiveOnL1 } = useDappStatus();
const limitWarning =
- stakingLimitLevel === LIMIT_LEVEL.WARN && isDappActive
+ stakingLimitLevel === LIMIT_LEVEL.WARN && isDappActiveOnL1
? 'Stake limit is almost exhausted. Your transaction may not go through.'
: null;
const limitError =
- stakingLimitLevel === LIMIT_LEVEL.REACHED && isDappActive
+ stakingLimitLevel === LIMIT_LEVEL.REACHED && isDappActiveOnL1
? 'Stake limit is exhausted. Please wait until the limit is restored.'
: null;
diff --git a/shared/hooks/use-token-address.ts b/shared/hooks/use-token-address.ts
new file mode 100644
index 000000000..986524e60
--- /dev/null
+++ b/shared/hooks/use-token-address.ts
@@ -0,0 +1,32 @@
+import { useLidoSDK } from 'providers/lido-sdk';
+import {
+ CHAINS,
+ LIDO_L2_CONTRACT_ADDRESSES,
+ LIDO_L2_CONTRACT_NAMES,
+} from '@lidofinance/lido-ethereum-sdk/common';
+import invariant from 'tiny-invariant';
+import {
+ CHAINS as OLD_CHAINS,
+ TOKENS,
+ getTokenAddress,
+} from '@lido-sdk/constants';
+
+import type { Address } from 'viem';
+
+// TODO rework this and sdk to get all addresses sync way
+export const useTokenAddress = (token: string): Address => {
+ const { core, isL2 } = useLidoSDK();
+ const tokenName = token.toLocaleLowerCase();
+ if (isL2) {
+ const address =
+ LIDO_L2_CONTRACT_ADDRESSES[core.chainId as CHAINS]?.[
+ tokenName as LIDO_L2_CONTRACT_NAMES
+ ];
+ invariant(address, `Do not have address for ${token} on ${core.chainId}`);
+ return address;
+ }
+ return getTokenAddress(
+ core.chainId as unknown as OLD_CHAINS,
+ token.toLocaleUpperCase() as TOKENS,
+ ) as Address;
+};
diff --git a/shared/hooks/use-wstETH-by-stETH-on-l2.ts b/shared/hooks/use-wstETH-by-stETH-on-l2.ts
new file mode 100644
index 000000000..2f30ae1a0
--- /dev/null
+++ b/shared/hooks/use-wstETH-by-stETH-on-l2.ts
@@ -0,0 +1,27 @@
+import { BigNumber } from 'ethers';
+import useSWR from 'swr';
+
+import { useLidoSDK } from 'providers/lido-sdk';
+
+export const useWstETHByStETHOnL2 = (steth: BigNumber | undefined) => {
+ const { l2 } = useLidoSDK();
+
+ const { data, error, isValidating, mutate } = useSWR(
+ // if key is null, SWR will not fetch any data.
+ steth
+ ? ['[swr:use-wsteth-by-steth-l2]', steth.toBigInt(), l2.core.chainId]
+ : null,
+ (_key: string, amount: bigint) => {
+ return l2.steth.convertToShares(amount);
+ },
+ );
+
+ return {
+ // Make usable for UnwrapStats component (in future can be used like bigint)
+ data: typeof data !== 'undefined' ? BigNumber.from(data) : undefined,
+ initialLoading: isValidating && !data && !error,
+ loading: isValidating,
+ error,
+ update: mutate,
+ };
+};
diff --git a/shared/hooks/useApprove.ts b/shared/hooks/useApproveOnL1.ts
similarity index 88%
rename from shared/hooks/useApprove.ts
rename to shared/hooks/useApproveOnL1.ts
index 3d26c6043..cbda6b72f 100644
--- a/shared/hooks/useApprove.ts
+++ b/shared/hooks/useApproveOnL1.ts
@@ -29,10 +29,10 @@ export type UseApproveResponse = {
needsApprove: boolean;
} & ReturnType;
-export const useApprove = (
+export const useApproveOnL1 = (
amount: BigNumber,
- token: string,
- spender: string,
+ token?: string,
+ spender?: string,
owner?: string,
): UseApproveResponse => {
const { providerWeb3, account } = useSDK();
@@ -40,8 +40,7 @@ export const useApprove = (
const waitForTx = useTxConfirmation();
const mergedOwner = owner ?? account;
- invariant(token != null, 'Token is required');
- invariant(spender != null, 'Spender is required');
+ const enabled = !!(token && spender && mergedOwner);
const allowanceQuery = useAllowance({
token: token as Address,
@@ -50,13 +49,18 @@ export const useApprove = (
});
const needsApprove = Boolean(
- allowanceQuery.data && !amount.isZero() && amount.gt(allowanceQuery.data),
+ enabled &&
+ allowanceQuery.data &&
+ !amount.isZero() &&
+ amount.gt(allowanceQuery.data),
);
const approve = useCallback(
async ({ onTxStart, onTxSent, onTxAwaited } = {}) => {
invariant(providerWeb3 != null, 'Web3 provider is required');
invariant(account, 'account is required');
+ invariant(token != null, 'Token is required');
+ invariant(spender != null, 'Spender is required');
await onTxStart?.();
const contractWeb3 = getERC20Contract(token, providerWeb3.getSigner());
const isMultisig = await isContract(account, staticRpcProvider);
@@ -75,7 +79,7 @@ export const useApprove = (
};
const approveTxHash = await runWithTransactionLogger(
- 'Approve signing',
+ 'Approve signing on L1',
processApproveTx,
);
await onTxSent?.(approveTxHash);
diff --git a/shared/hooks/useIsContract.ts b/shared/hooks/useIsContract.ts
deleted file mode 100644
index a6dfc8a2d..000000000
--- a/shared/hooks/useIsContract.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useEthereumSWR } from '@lido-sdk/react';
-import { STRATEGY_IMMUTABLE } from 'consts/swr-strategies';
-
-export const useIsContract = (
- account?: string | null,
-): { isLoading: boolean; isContract: boolean } => {
- // eth_getCode returns hex string of bytecode at address
- // for accounts it's "0x"
- // for contract it's potentially very long hex (can't be safely&quickly parsed)
- const result = useEthereumSWR({
- shouldFetch: !!account,
- method: 'getCode',
- params: [account, 'latest'],
- config: STRATEGY_IMMUTABLE,
- });
-
- return {
- isLoading: result.initialLoading,
- isContract: result.data ? result.data !== '0x' : false,
- };
-};
diff --git a/shared/hooks/useIsMultisig.ts b/shared/hooks/useIsMultisig.ts
index d4cd13374..f3da9f737 100644
--- a/shared/hooks/useIsMultisig.ts
+++ b/shared/hooks/useIsMultisig.ts
@@ -1,10 +1,8 @@
import { useAccount } from 'wagmi';
-import { useIsContract } from 'shared/hooks';
+import { useIsContract } from 'shared/hooks/use-is-contract';
export const useIsMultisig = () => {
const { address } = useAccount();
- const { isContract: isMultisig, isLoading } = useIsContract(
- address ?? undefined,
- );
+ const { data: isMultisig, isLoading } = useIsContract(address ?? undefined);
return { isMultisig, isLoading };
};
diff --git a/shared/hooks/useMaxGasPrice.ts b/shared/hooks/useMaxGasPrice.ts
index 7a206bc22..dae7cd3b5 100644
--- a/shared/hooks/useMaxGasPrice.ts
+++ b/shared/hooks/useMaxGasPrice.ts
@@ -1,37 +1,50 @@
-import { useLidoSWR } from '@lido-sdk/react';
+import { useFeeHistory } from 'wagmi';
+import { BigNumber } from 'ethers';
+import type { GetFeeHistoryReturnType } from 'viem';
-import { getFeeData } from 'utils/getFeeData';
-import { STRATEGY_LAZY } from 'consts/swr-strategies';
+const REWARD_PERCENTILES = [25];
-import { useCurrentStaticRpcProvider } from './use-current-static-rpc-provider';
+const feeHistoryToMaxFee = ({
+ reward,
+ baseFeePerGas,
+}: GetFeeHistoryReturnType) => {
+ const maxPriorityFeePerGas = reward
+ ? reward?.map((fees) => fees[0]).reduce((sum, fee) => sum + fee) /
+ BigInt(reward.length)
+ : BigInt(0);
-export const useMaxGasPrice = () => {
- const { chainId, staticRpcProvider } = useCurrentStaticRpcProvider();
+ const lastBaseFeePerGas = baseFeePerGas[0];
+
+ // we have to multiply by 2 until we find a reliable way to predict baseFee change
+ const maxFeePerGas = lastBaseFeePerGas * BigInt(2) + maxPriorityFeePerGas;
+ return BigNumber.from(maxFeePerGas);
+};
- const swr = useLidoSWR(
- ['swr:max-gas-price', chainId],
- async () => {
- const { maxFeePerGas } = await getFeeData(staticRpcProvider);
- return maxFeePerGas;
+export const useMaxGasPrice = () => {
+ const { data, isLoading, error, isFetching, refetch } = useFeeHistory({
+ blockCount: 5,
+ blockTag: 'pending',
+ rewardPercentiles: REWARD_PERCENTILES,
+ query: {
+ select: feeHistoryToMaxFee,
},
- STRATEGY_LAZY,
- );
+ });
return {
get maxGasPrice() {
- return swr.data;
+ return data;
},
get initialLoading() {
- return swr.initialLoading;
+ return isLoading;
},
get error() {
- return swr.error;
+ return error;
},
get loading() {
- return swr.loading;
+ return isFetching;
},
update() {
- return swr.update;
+ return refetch();
},
};
};
diff --git a/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx b/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx
index 0f03692dd..25b7c4706 100644
--- a/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx
+++ b/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx
@@ -1,8 +1,7 @@
import styled from 'styled-components';
-import { useTokenAddress } from '@lido-sdk/react';
-import { TOKENS } from '@lido-sdk/constants';
import { InlineLoader } from '@lidofinance/lido-ui';
+import { useTokenAddress } from 'shared/hooks/use-token-address';
import { TxAmount } from '../tx-stages-parts/tx-amount';
import { SuccessText } from '../tx-stages-parts/success-text';
import { TxStageSuccess } from '../tx-stages-basic';
@@ -37,10 +36,7 @@ export const TxStageOperationSucceedBalanceShown = ({
txHash,
footer,
}: TxStageOperationSucceedBalanceShownProps) => {
- const stethAddress = useTokenAddress(TOKENS.STETH);
- const wstethAddress = useTokenAddress(TOKENS.WSTETH);
- const tokenToWalletAddress =
- balanceToken === 'wstETH' ? wstethAddress : stethAddress;
+ const tokenToWalletAddress = useTokenAddress(balanceToken);
const balanceEl = balance && (
diff --git a/shared/wallet/button/button.tsx b/shared/wallet/button/button.tsx
index 7d6e9a451..9b417a9bb 100644
--- a/shared/wallet/button/button.tsx
+++ b/shared/wallet/button/button.tsx
@@ -21,7 +21,7 @@ export const Button: FC = (props) => {
const isMobile = useBreakpoint('md');
const { address } = useAccount();
- const { isDappActive } = useDappStatus();
+ const { isDappActiveAndNetworksMatched } = useDappStatus();
const { openModal } = useWalletModal();
const { data: balance, isLoading } = useEthereumBalance();
@@ -32,7 +32,9 @@ export const Button: FC = (props) => {
variant="text"
color="secondary"
onClick={() => openModal({})}
- $isAddPaddingLeft={!isLoading && !isDappActive && !isMobile}
+ $isAddPaddingLeft={
+ !isLoading && !isDappActiveAndNetworksMatched && !isMobile
+ }
{...rest}
>
@@ -40,7 +42,7 @@ export const Button: FC = (props) => {
{isLoading ? (
) : (
- isDappActive && (
+ isDappActiveAndNetworksMatched && (
)`
flex-shrink: 1;
min-width: unset;
overflow: hidden;
+ border-radius: 10px;
${({ $isAddPaddingLeft }) => ($isAddPaddingLeft ? `padding-left: 9px` : '')};
`;
diff --git a/shared/wallet/card/types.ts b/shared/wallet/card/types.ts
index 24a196459..854c970cc 100644
--- a/shared/wallet/card/types.ts
+++ b/shared/wallet/card/types.ts
@@ -2,7 +2,9 @@ import { Component } from 'types';
import { BlockProps } from '@lidofinance/lido-ui';
import { FC } from 'react';
-export type WalletCardComponent = FC;
+export type WalletCardComponent = FC<
+ BlockProps & { error?: string | undefined }
+>;
export type WalletCardRowComponent = Component<'div'>;
diff --git a/shared/wallet/unsupported-chain-button/unsupported-chain-button.tsx b/shared/wallet/disabled-button/disabled-button.tsx
similarity index 55%
rename from shared/wallet/unsupported-chain-button/unsupported-chain-button.tsx
rename to shared/wallet/disabled-button/disabled-button.tsx
index 72f8ccc3e..6e931ea76 100644
--- a/shared/wallet/unsupported-chain-button/unsupported-chain-button.tsx
+++ b/shared/wallet/disabled-button/disabled-button.tsx
@@ -1,12 +1,12 @@
-import { FC } from 'react';
+import { FC, PropsWithChildren } from 'react';
import { ButtonProps } from '@lidofinance/lido-ui';
import { ButtonStyle } from './styles';
-export const UnsupportedChainButton: FC = (props) => {
+export const DisabledButton: FC> = (props) => {
return (
- Unsupported chain
+ {props.children}
);
};
diff --git a/shared/wallet/unsupported-chain-button/styles.tsx b/shared/wallet/disabled-button/styles.tsx
similarity index 100%
rename from shared/wallet/unsupported-chain-button/styles.tsx
rename to shared/wallet/disabled-button/styles.tsx
diff --git a/shared/wallet/fallback/fallback.tsx b/shared/wallet/fallback/fallback.tsx
index 1ab6c72ed..e8831d220 100644
--- a/shared/wallet/fallback/fallback.tsx
+++ b/shared/wallet/fallback/fallback.tsx
@@ -6,10 +6,10 @@ import { useErrorMessage } from './useErrorMessage';
export const Fallback: WalletCardComponent = (props) => {
const error = useErrorMessage();
- if (error) {
+ if (props.error || error) {
return (
- {error}
+ {props.error || error || ''}
);
}
diff --git a/shared/wallet/index.ts b/shared/wallet/index.ts
index e7f23c038..b4d2cb5cc 100644
--- a/shared/wallet/index.ts
+++ b/shared/wallet/index.ts
@@ -3,4 +3,4 @@ export { Card, CardRow, CardBalance, CardAccount } from './card/card';
export { Connect } from './connect/connect';
export { Fallback } from './fallback/fallback';
export { LidoMultichainFallback } from './lido-multichain-fallback/lido-multichain-fallback';
-export { UnsupportedChainButton } from './unsupported-chain-button/unsupported-chain-button';
+export { DisabledButton } from './disabled-button/disabled-button';
diff --git a/shared/wallet/lido-multichain-fallback/lido-multichain-fallback.tsx b/shared/wallet/lido-multichain-fallback/lido-multichain-fallback.tsx
index 534e9479f..0d1620028 100644
--- a/shared/wallet/lido-multichain-fallback/lido-multichain-fallback.tsx
+++ b/shared/wallet/lido-multichain-fallback/lido-multichain-fallback.tsx
@@ -1,5 +1,7 @@
import { FC, useMemo } from 'react';
import { useAccount } from 'wagmi';
+import { useRouter } from 'next/router';
+
import { BlockProps, Link } from '@lidofinance/lido-ui';
import { ReactComponent as ArbitrumLogo } from 'assets/icons/lido-multichain/arbitrum.svg';
@@ -17,12 +19,14 @@ import { config } from 'config';
import { useUserConfig } from 'config/user-config';
import { CHAINS, LIDO_MULTICHAIN_CHAINS } from 'consts/chains';
import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events';
+import { trackMatomoEvent } from 'utils/track-matomo-event';
+import { OPTIMISM, ETHEREUM } from 'providers/dapp-chain';
+import { capitalizeFirstLetter } from 'utils/capitalize-string';
import { Wrap, TextStyle, ButtonStyle } from './styles';
-import { trackMatomoEvent } from '../../../utils/track-matomo-event';
export type LidoMultichainFallbackComponent = FC<
- { textEnding: string } & BlockProps
+ { textEnding: string; chainId?: number | undefined } & BlockProps
>;
const multichainLogos = {
@@ -46,8 +50,11 @@ const getChainLogo = (chainId: LIDO_MULTICHAIN_CHAINS) => {
export const LidoMultichainFallback: LidoMultichainFallbackComponent = (
props,
) => {
- const { chainId } = useAccount();
+ const { chainId: chainIdWagmin } = useAccount();
const { defaultChain } = useUserConfig();
+ const router = useRouter();
+
+ const chainId = props.chainId || chainIdWagmin;
const defaultChainName = useMemo(() => {
if (CHAINS[defaultChain] === 'Mainnet') return 'Ethereum';
@@ -59,14 +66,21 @@ export const LidoMultichainFallback: LidoMultichainFallbackComponent = (
return (!!chainId && LIDO_MULTICHAIN_CHAINS[chainId]) || 'unknown';
}, [chainId]);
+ const switchToText = useMemo(() => {
+ if (router.pathname === '/wrap/[[...mode]]') {
+ return `${capitalizeFirstLetter(ETHEREUM)}/${capitalizeFirstLetter(OPTIMISM)}`;
+ } else {
+ return defaultChainName;
+ }
+ }, [router.pathname, defaultChainName]);
+
return (
{getChainLogo(chainId as LIDO_MULTICHAIN_CHAINS)}
- You’re currently on {lidoMultichainChainName}.
+ You are currently on {lidoMultichainChainName}.
- Explore Lido Multichain or switch to {defaultChainName}{' '}
- {props.textEnding}.
+ Explore Lido Multichain or switch to {switchToText} {props.textEnding}.
{
- const { address } = useAccount();
+ const { address, chainId } = useAccount();
const { connectorName } = useConnectorInfo();
const { disconnect } = useDisconnect();
+ const { defaultChain: defaultChainId } = useUserConfig();
const handleDisconnect = useCallback(() => {
disconnect?.();
@@ -34,7 +37,16 @@ export const WalletModal: ModalComponentType = ({ onClose, ...props }) => {
}, [disconnect, onClose]);
const handleCopy = useCopyToClipboard(address ?? '');
- const handleEtherscan = useEtherscanOpen(address ?? '', 'address');
+ const handleEtherscan = useCallback(() => {
+ if (!chainId) return;
+
+ const link = getEtherscanAddressLink(
+ chainId,
+ address ?? '',
+ defaultChainId,
+ );
+ openWindow(link);
+ }, [address, chainId, defaultChainId]);
useEffect(() => {
// Close the modal if a wallet was somehow disconnected while the modal was open
diff --git a/utils/apply-gas-limit-ratio.ts b/utils/apply-gas-limit-ratio.ts
index c0d3b9c54..8103288a4 100644
--- a/utils/apply-gas-limit-ratio.ts
+++ b/utils/apply-gas-limit-ratio.ts
@@ -5,3 +5,8 @@ export const applyGasLimitRatio = (gasLimit: BigNumber): BigNumber =>
gasLimit
.mul(config.SUBMIT_EXTRA_GAS_TRANSACTION_RATIO * config.PRECISION)
.div(config.PRECISION);
+
+export const applyGasLimitRatioBigInt = (gasLimit: bigint): bigint =>
+ (gasLimit *
+ BigInt(config.SUBMIT_EXTRA_GAS_TRANSACTION_RATIO * config.PRECISION)) /
+ BigInt(config.PRECISION);
diff --git a/utils/capitalize-string.ts b/utils/capitalize-string.ts
new file mode 100644
index 000000000..51958ded5
--- /dev/null
+++ b/utils/capitalize-string.ts
@@ -0,0 +1,4 @@
+export const capitalizeFirstLetter = (str: string): string => {
+ if (!str) return str;
+ return str.charAt(0).toUpperCase() + str.toLowerCase().slice(1);
+};
diff --git a/utils/convert-to-big-number.ts b/utils/convert-to-big-number.ts
new file mode 100644
index 000000000..cddf366cd
--- /dev/null
+++ b/utils/convert-to-big-number.ts
@@ -0,0 +1,8 @@
+import { BigNumber } from 'ethers';
+
+export const convertToBigNumber = (value: bigint | BigNumber): BigNumber => {
+ if (typeof value === 'bigint') {
+ return BigNumber.from(value.toString());
+ }
+ return value;
+};
diff --git a/utils/get-etherscan-address-link.ts b/utils/get-etherscan-address-link.ts
new file mode 100644
index 000000000..66d959389
--- /dev/null
+++ b/utils/get-etherscan-address-link.ts
@@ -0,0 +1,26 @@
+import { getEtherscanLink as getEtherscanLinkSDK } from '@lido-sdk/helpers';
+import { CHAINS as CHAINS_SDK } from '@lido-sdk/constants';
+
+import { CHAINS } from 'consts/chains';
+
+export const getEtherscanAddressLink = (
+ chainId: number,
+ address: string,
+ fallbackChainId: number,
+): string => {
+ if (chainId === CHAINS.OptimismSepolia) {
+ return `https://sepolia-optimistic.etherscan.io/address/${address}`;
+ } else if (chainId === CHAINS.Optimism) {
+ return `https://optimistic.etherscan.io/address/${address}`;
+ }
+
+ try {
+ return getEtherscanLinkSDK(chainId as CHAINS_SDK, address, 'address');
+ } catch {
+ return getEtherscanLinkSDK(
+ fallbackChainId as CHAINS_SDK,
+ address,
+ 'address',
+ );
+ }
+};
diff --git a/utils/get-etherscan-tx-link.ts b/utils/get-etherscan-tx-link.ts
new file mode 100644
index 000000000..f7ca4fe46
--- /dev/null
+++ b/utils/get-etherscan-tx-link.ts
@@ -0,0 +1,18 @@
+import { getEtherscanTxLink as getEtherscanTxLinkSDK } from '@lido-sdk/helpers';
+import { CHAINS as CHAINS_SDK } from '@lido-sdk/constants';
+
+import { CHAINS } from 'consts/chains';
+
+export const getEtherscanTxLink = (
+ chainId: CHAINS | CHAINS_SDK,
+ hash: string,
+): string => {
+ // TODO: use viem for getting explorer
+ if (chainId === CHAINS.OptimismSepolia) {
+ return `https://sepolia-optimistic.etherscan.io/tx/${hash}`;
+ } else if (chainId === CHAINS.Optimism) {
+ return `https://optimistic.etherscan.io/tx/${hash}`;
+ }
+
+ return getEtherscanTxLinkSDK(chainId as CHAINS_SDK, hash);
+};
diff --git a/utils/getNFTUrl.ts b/utils/getNFTUrl.ts
index b7de349a3..91bfc243a 100644
--- a/utils/getNFTUrl.ts
+++ b/utils/getNFTUrl.ts
@@ -13,7 +13,11 @@ export const NFT_URL_PREFIX_BY_NETWORK: {
export const getNFTUrl = (tokenId: string, chainId?: CHAINS) => {
if (!chainId) return '';
- const contractAddress = getWithdrawalQueueAddress(chainId);
+ try {
+ const contractAddress = getWithdrawalQueueAddress(chainId);
- return NFT_URL_PREFIX_BY_NETWORK[chainId]?.(tokenId, contractAddress) || '';
+ return NFT_URL_PREFIX_BY_NETWORK[chainId]?.(tokenId, contractAddress) || '';
+ } catch {
+ return '';
+ }
};
diff --git a/utils/isContract.ts b/utils/isContract.ts
index 0ff51ed6c..f3aaf7184 100644
--- a/utils/isContract.ts
+++ b/utils/isContract.ts
@@ -1,5 +1,8 @@
import { type Provider } from '@ethersproject/abstract-provider';
+/**
+ * @deprecated only work for L1, use hooks/use-is-contract
+ */
export const isContract = async (
address: string,
provider: Provider,
diff --git a/utilsApi/contractAddressesMetricsMap.ts b/utilsApi/contractAddressesMetricsMap.ts
index 47f722219..6e537c361 100644
--- a/utilsApi/contractAddressesMetricsMap.ts
+++ b/utilsApi/contractAddressesMetricsMap.ts
@@ -21,14 +21,22 @@ import {
PartialCurveAbiAbi__factory,
PartialStakingRouterAbi__factory,
LidoLocatorAbi__factory,
+ L2StethAbi__factory,
+ L2WstesthAbi__factory,
} from 'generated';
import { getStakingRouterAddress } from 'consts/staking-router';
import { MAINNET_CURVE } from 'features/rewards/hooks/use-steth-eth-rate';
-import { LIDO_LOCATOR_BY_CHAIN } from '@lidofinance/lido-ethereum-sdk';
+import {
+ LIDO_LOCATOR_BY_CHAIN,
+ LIDO_L2_CONTRACT_ADDRESSES,
+ CHAINS as CHAIN_SDK,
+} from '@lidofinance/lido-ethereum-sdk/common';
export const CONTRACT_NAMES = {
stETH: 'stETH',
wstETH: 'wstETH',
+ L2stETH: 'L2stETH',
+ L2wstETH: 'L2wstETH',
WithdrawalQueue: 'WithdrawalQueue',
Aggregator: 'Aggregator',
AggregatorStEthUsdPriceFeed: 'AggregatorStEthUsdPriceFeed',
@@ -47,6 +55,8 @@ export const METRIC_CONTRACT_ABIS = {
[CONTRACT_NAMES.StakingRouter]: PartialStakingRouterAbi__factory.abi,
[CONTRACT_NAMES.StethCurve]: PartialCurveAbiAbi__factory.abi,
[CONTRACT_NAMES.LidoLocator]: LidoLocatorAbi__factory.abi,
+ [CONTRACT_NAMES.L2stETH]: L2StethAbi__factory.abi,
+ [CONTRACT_NAMES.L2wstETH]: L2WstesthAbi__factory.abi,
} as const;
export const getMetricContractInterface = memoize(
@@ -55,7 +65,7 @@ export const getMetricContractInterface = memoize(
);
const getAddressOrNull = <
- G extends (...args: any) => string,
+ G extends (...args: any) => string | null,
A extends Parameters,
>(
getter: G,
@@ -69,6 +79,7 @@ const getAddressOrNull = <
}
};
+// TODO rework to viem and remove typechain
export const METRIC_CONTRACT_ADDRESSES = (
config.supportedChains as CHAINS[]
).reduce(
@@ -102,11 +113,23 @@ export const METRIC_CONTRACT_ADDRESSES = (
),
[CONTRACT_NAMES.StethCurve]: getAddressOrNull((chainId: CHAINS) => {
if (chainId === 1) return MAINNET_CURVE;
- else throw new Error('no contract address');
+ return null;
}, chainId),
[CONTRACT_NAMES.LidoLocator]: getAddressOrNull((chainId: CHAINS) => {
return (LIDO_LOCATOR_BY_CHAIN as any)[chainId] as string;
}, chainId),
+ [CONTRACT_NAMES.L2stETH]: getAddressOrNull((chainId: CHAINS) => {
+ const chainIDSDK = chainId as unknown as CHAIN_SDK;
+ return (
+ (LIDO_L2_CONTRACT_ADDRESSES[chainIDSDK]?.['steth'] as string) ?? null
+ );
+ }, chainId),
+ [CONTRACT_NAMES.L2wstETH]: getAddressOrNull((chainId: CHAINS) => {
+ const chainIDSDK = chainId as unknown as CHAIN_SDK;
+ return (
+ (LIDO_L2_CONTRACT_ADDRESSES[chainIDSDK]?.['wsteth'] as string) ?? null
+ );
+ }, chainId),
};
return {
...mapped,
@@ -131,6 +154,18 @@ export const METRIC_CONTRACT_EVENT_ADDRESSES = (
chainId,
TOKENS.WSTETH,
),
+ [CONTRACT_NAMES.L2stETH]: getAddressOrNull((chainId: CHAINS) => {
+ const chainIDSDK = chainId as unknown as CHAIN_SDK;
+ return (
+ (LIDO_L2_CONTRACT_ADDRESSES[chainIDSDK]?.['steth'] as string) ?? null
+ );
+ }, chainId),
+ [CONTRACT_NAMES.L2wstETH]: getAddressOrNull((chainId: CHAINS) => {
+ const chainIDSDK = chainId as unknown as CHAIN_SDK;
+ return (
+ (LIDO_L2_CONTRACT_ADDRESSES[chainIDSDK]?.['wsteth'] as string) ?? null
+ );
+ }, chainId),
};
return {
...mapped,
diff --git a/yarn.lock b/yarn.lock
index 7e1f8d63a..cc6865b8a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2348,10 +2348,10 @@
resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.45.1.tgz#54cbd893c92c06f7ad605ebe0fd8059fc326f180"
integrity sha512-ugkRCI0BPFjWY2h/cDcM8IiibQ8i/wycUgVk2C+E7gCZkjL1YPxGZdnQkfFgIHXAtPhw2YeKHP+VC6HiZpVrZw==
-"@lidofinance/lido-ethereum-sdk@3.5.0-alpha.1":
- version "3.5.0-alpha.1"
- resolved "https://registry.yarnpkg.com/@lidofinance/lido-ethereum-sdk/-/lido-ethereum-sdk-3.5.0-alpha.1.tgz#c670e863b06c4ab50b438ab717012be720d7e1b4"
- integrity sha512-MlAeCnKTnGPzkGZEct2r3ADMSKfBBz1e2g9qcpuDo40RSTCiu2XX5H58pA9aatoT4UARFhFU8qR4THSIzTDAhg==
+"@lidofinance/lido-ethereum-sdk@3.5.0-alpha.3":
+ version "3.5.0-alpha.3"
+ resolved "https://registry.yarnpkg.com/@lidofinance/lido-ethereum-sdk/-/lido-ethereum-sdk-3.5.0-alpha.3.tgz#a503a0686f8e4e5438995149d1316b0c98d09cfe"
+ integrity sha512-FvrW7Dk5n802qES5F5nZo5NzTw+pWBh3jSMJltOYCtsaIO97bUXkqm6p4cfURnvj1coI5MH00BqJuUj7yDI2NQ==
dependencies:
"@ethersproject/bytes" "^5.7.0"
graphql "^16.8.1"
@@ -10418,7 +10418,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -10435,6 +10435,15 @@ string-width@^2.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -10509,7 +10518,7 @@ stringify-entities@^3.0.1:
character-entities-legacy "^1.0.0"
xtend "^4.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -10530,6 +10539,13 @@ strip-ansi@^5.1.0:
dependencies:
ansi-regex "^4.1.0"
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -11569,19 +11585,19 @@ winston@*:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+wrap-ansi@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+ integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^6.2.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
- integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"