diff --git a/.env.defaults b/.env.defaults index 161bd5cce2..429b933f69 100644 --- a/.env.defaults +++ b/.env.defaults @@ -11,6 +11,7 @@ HIDE_IMPORT_LEDGER=false GAS_PRICE_POOLING_FREQUENCY=120 ETHEREUM_NETWORK=mainnet PERSIST_UI_LOCATION=false +EARN_COMING_SOON=true WEBSITE_ORIGIN=https://tally.cash USE_MAINNET_FORK=false MAINNET_FORK_URL="http://127.0.0.1:8545" diff --git a/background/constants/index.ts b/background/constants/index.ts index c878dbfaae..f5a3697aaf 100644 --- a/background/constants/index.ts +++ b/background/constants/index.ts @@ -26,5 +26,7 @@ export const DAY = 24 * HOUR export const COMMUNITY_MULTISIG_ADDRESS = "0x99b36fDbC582D113aF36A21EBa06BFEAb7b9bE12" +export const doggoTokenDecimalDigits = 18 + export * from "./currencies" export * from "./networks" diff --git a/background/features/features.ts b/background/features/features.ts index 6db6ccb095..3ea0cf45e3 100644 --- a/background/features/features.ts +++ b/background/features/features.ts @@ -6,3 +6,4 @@ export const HIDE_CREATE_PHRASE = process.env.HIDE_CREATE_PHRASE === "true" export const HIDE_IMPORT_LEDGER = process.env.HIDE_IMPORT_LEDGER === "true" export const PERSIST_UI_LOCATION = process.env.PERSIST_UI_LOCATION === "true" export const USE_MAINNET_FORK = process.env.USE_MAINNET_FORK === "true" +export const EARN_COMING_SOON = process.env.EARN_COMING_SOON === "true" diff --git a/background/lib/approvalTarget.ts b/background/lib/approvalTarget.ts new file mode 100644 index 0000000000..887b8370af --- /dev/null +++ b/background/lib/approvalTarget.ts @@ -0,0 +1,162 @@ +const APPROVAL_TARGET_ABI = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "PERMIT_AND_TRANSFER_FROM_TYPEHASH", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cachedChainId", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cachedDomainSeparator", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "nonces", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "erc20", + type: "address", + }, + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + 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: "permitAndTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "typeHashDigest", + type: "bytes32", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "recoverFromTypeHashSignature", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] + +export default APPROVAL_TARGET_ABI diff --git a/background/lib/yearnVault.ts b/background/lib/yearnVault.ts new file mode 100644 index 0000000000..a8d66b66c6 --- /dev/null +++ b/background/lib/yearnVault.ts @@ -0,0 +1,882 @@ +const YEARN_VAULT_ABI = [ + { + name: "Transfer", + inputs: [ + { name: "sender", type: "address", indexed: true }, + { name: "receiver", type: "address", indexed: true }, + { name: "value", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "Approval", + inputs: [ + { name: "owner", type: "address", indexed: true }, + { name: "spender", type: "address", indexed: true }, + { name: "value", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyAdded", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "debtRatio", type: "uint256", indexed: false }, + { name: "minDebtPerHarvest", type: "uint256", indexed: false }, + { name: "maxDebtPerHarvest", type: "uint256", indexed: false }, + { name: "performanceFee", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyReported", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "gain", type: "uint256", indexed: false }, + { name: "loss", type: "uint256", indexed: false }, + { name: "debtPaid", type: "uint256", indexed: false }, + { name: "totalGain", type: "uint256", indexed: false }, + { name: "totalLoss", type: "uint256", indexed: false }, + { name: "totalDebt", type: "uint256", indexed: false }, + { name: "debtAdded", type: "uint256", indexed: false }, + { name: "debtRatio", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "UpdateGovernance", + inputs: [{ name: "governance", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateManagement", + inputs: [{ name: "management", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateGuestList", + inputs: [{ name: "guestList", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateRewards", + inputs: [{ name: "rewards", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateDepositLimit", + inputs: [{ name: "depositLimit", type: "uint256", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdatePerformanceFee", + inputs: [{ name: "performanceFee", type: "uint256", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateManagementFee", + inputs: [{ name: "managementFee", type: "uint256", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateGuardian", + inputs: [{ name: "guardian", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "EmergencyShutdown", + inputs: [{ name: "active", type: "bool", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateWithdrawalQueue", + inputs: [{ name: "queue", type: "address[20]", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdateDebtRatio", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "debtRatio", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdateMinDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "minDebtPerHarvest", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdateMaxDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "maxDebtPerHarvest", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdatePerformanceFee", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "performanceFee", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyMigrated", + inputs: [ + { name: "oldVersion", type: "address", indexed: true }, + { name: "newVersion", type: "address", indexed: true }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyRevoked", + inputs: [{ name: "strategy", type: "address", indexed: true }], + anonymous: false, + type: "event", + }, + { + name: "StrategyRemovedFromQueue", + inputs: [{ name: "strategy", type: "address", indexed: true }], + anonymous: false, + type: "event", + }, + { + name: "StrategyAddedToQueue", + inputs: [{ name: "strategy", type: "address", indexed: true }], + anonymous: false, + type: "event", + }, + { + stateMutability: "nonpayable", + type: "function", + name: "initialize", + inputs: [ + { name: "token", type: "address" }, + { name: "governance", type: "address" }, + { name: "rewards", type: "address" }, + { name: "nameOverride", type: "string" }, + { name: "symbolOverride", type: "string" }, + ], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "initialize", + inputs: [ + { name: "token", type: "address" }, + { name: "governance", type: "address" }, + { name: "rewards", type: "address" }, + { name: "nameOverride", type: "string" }, + { name: "symbolOverride", type: "string" }, + { name: "guardian", type: "address" }, + ], + outputs: [], + }, + { + stateMutability: "pure", + type: "function", + name: "apiVersion", + inputs: [], + outputs: [{ name: "", type: "string" }], + gas: 4546, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setName", + inputs: [{ name: "name", type: "string" }], + outputs: [], + gas: 107044, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setSymbol", + inputs: [{ name: "symbol", type: "string" }], + outputs: [], + gas: 71894, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setGovernance", + inputs: [{ name: "governance", type: "address" }], + outputs: [], + gas: 36365, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "acceptGovernance", + inputs: [], + outputs: [], + gas: 37637, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setManagement", + inputs: [{ name: "management", type: "address" }], + outputs: [], + gas: 37775, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setGuestList", + inputs: [{ name: "guestList", type: "address" }], + outputs: [], + gas: 37805, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setRewards", + inputs: [{ name: "rewards", type: "address" }], + outputs: [], + gas: 37835, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setLockedProfitDegration", + inputs: [{ name: "degration", type: "uint256" }], + outputs: [], + gas: 36519, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setDepositLimit", + inputs: [{ name: "limit", type: "uint256" }], + outputs: [], + gas: 37795, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setPerformanceFee", + inputs: [{ name: "fee", type: "uint256" }], + outputs: [], + gas: 37929, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setManagementFee", + inputs: [{ name: "fee", type: "uint256" }], + outputs: [], + gas: 37959, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setGuardian", + inputs: [{ name: "guardian", type: "address" }], + outputs: [], + gas: 39203, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setEmergencyShutdown", + inputs: [{ name: "active", type: "bool" }], + outputs: [], + gas: 39274, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setWithdrawalQueue", + inputs: [{ name: "queue", type: "address[20]" }], + outputs: [], + gas: 763950, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "transfer", + inputs: [ + { name: "receiver", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 76768, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "transferFrom", + inputs: [ + { name: "sender", type: "address" }, + { name: "receiver", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 116531, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "approve", + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 38271, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "increaseAllowance", + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 40312, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "decreaseAllowance", + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 40336, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "permit", + inputs: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + { name: "expiry", type: "uint256" }, + { name: "signature", type: "bytes" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 81264, + }, + { + stateMutability: "view", + type: "function", + name: "totalAssets", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 4098, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "deposit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "deposit", + inputs: [{ name: "_amount", type: "uint256" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "deposit", + inputs: [ + { name: "_amount", type: "uint256" }, + { name: "recipient", type: "address" }, + ], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "maxAvailableShares", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 383839, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [{ name: "maxShares", type: "uint256" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [ + { name: "maxShares", type: "uint256" }, + { name: "recipient", type: "address" }, + ], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [ + { name: "maxShares", type: "uint256" }, + { name: "recipient", type: "address" }, + { name: "maxLoss", type: "uint256" }, + ], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "pricePerShare", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 18195, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "addStrategy", + inputs: [ + { name: "strategy", type: "address" }, + { name: "debtRatio", type: "uint256" }, + { name: "minDebtPerHarvest", type: "uint256" }, + { name: "maxDebtPerHarvest", type: "uint256" }, + { name: "performanceFee", type: "uint256" }, + ], + outputs: [], + gas: 1485796, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyDebtRatio", + inputs: [ + { name: "strategy", type: "address" }, + { name: "debtRatio", type: "uint256" }, + ], + outputs: [], + gas: 115193, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyMinDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address" }, + { name: "minDebtPerHarvest", type: "uint256" }, + ], + outputs: [], + gas: 42441, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyMaxDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address" }, + { name: "maxDebtPerHarvest", type: "uint256" }, + ], + outputs: [], + gas: 42471, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyPerformanceFee", + inputs: [ + { name: "strategy", type: "address" }, + { name: "performanceFee", type: "uint256" }, + ], + outputs: [], + gas: 41251, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "migrateStrategy", + inputs: [ + { name: "oldVersion", type: "address" }, + { name: "newVersion", type: "address" }, + ], + outputs: [], + gas: 1141468, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "revokeStrategy", + inputs: [], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "revokeStrategy", + inputs: [{ name: "strategy", type: "address" }], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "addStrategyToQueue", + inputs: [{ name: "strategy", type: "address" }], + outputs: [], + gas: 1199804, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "removeStrategyFromQueue", + inputs: [{ name: "strategy", type: "address" }], + outputs: [], + gas: 23088703, + }, + { + stateMutability: "view", + type: "function", + name: "debtOutstanding", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "debtOutstanding", + inputs: [{ name: "strategy", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "creditAvailable", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "creditAvailable", + inputs: [{ name: "strategy", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "availableDepositLimit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 9551, + }, + { + stateMutability: "view", + type: "function", + name: "expectedReturn", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "expectedReturn", + inputs: [{ name: "strategy", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "report", + inputs: [ + { name: "gain", type: "uint256" }, + { name: "loss", type: "uint256" }, + { name: "_debtPayment", type: "uint256" }, + ], + outputs: [{ name: "", type: "uint256" }], + gas: 1015170, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "sweep", + inputs: [{ name: "token", type: "address" }], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "sweep", + inputs: [ + { name: "token", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [], + }, + { + stateMutability: "view", + type: "function", + name: "name", + inputs: [], + outputs: [{ name: "", type: "string" }], + gas: 8750, + }, + { + stateMutability: "view", + type: "function", + name: "symbol", + inputs: [], + outputs: [{ name: "", type: "string" }], + gas: 7803, + }, + { + stateMutability: "view", + type: "function", + name: "decimals", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2408, + }, + { + stateMutability: "view", + type: "function", + name: "precisionFactor", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2438, + }, + { + stateMutability: "view", + type: "function", + name: "balanceOf", + inputs: [{ name: "arg0", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + gas: 2683, + }, + { + stateMutability: "view", + type: "function", + name: "allowance", + inputs: [ + { name: "arg0", type: "address" }, + { name: "arg1", type: "address" }, + ], + outputs: [{ name: "", type: "uint256" }], + gas: 2928, + }, + { + stateMutability: "view", + type: "function", + name: "totalSupply", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2528, + }, + { + stateMutability: "view", + type: "function", + name: "token", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2558, + }, + { + stateMutability: "view", + type: "function", + name: "governance", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2588, + }, + { + stateMutability: "view", + type: "function", + name: "management", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2618, + }, + { + stateMutability: "view", + type: "function", + name: "guardian", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2648, + }, + { + stateMutability: "view", + type: "function", + name: "guestList", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2678, + }, + { + stateMutability: "view", + type: "function", + name: "strategies", + inputs: [{ name: "arg0", type: "address" }], + outputs: [ + { name: "performanceFee", type: "uint256" }, + { name: "activation", type: "uint256" }, + { name: "debtRatio", type: "uint256" }, + { name: "minDebtPerHarvest", type: "uint256" }, + { name: "maxDebtPerHarvest", type: "uint256" }, + { name: "lastReport", type: "uint256" }, + { name: "totalDebt", type: "uint256" }, + { name: "totalGain", type: "uint256" }, + { name: "totalLoss", type: "uint256" }, + ], + gas: 11031, + }, + { + stateMutability: "view", + type: "function", + name: "withdrawalQueue", + inputs: [{ name: "arg0", type: "uint256" }], + outputs: [{ name: "", type: "address" }], + gas: 2847, + }, + { + stateMutability: "view", + type: "function", + name: "emergencyShutdown", + inputs: [], + outputs: [{ name: "", type: "bool" }], + gas: 2768, + }, + { + stateMutability: "view", + type: "function", + name: "depositLimit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2798, + }, + { + stateMutability: "view", + type: "function", + name: "debtRatio", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2828, + }, + { + stateMutability: "view", + type: "function", + name: "totalDebt", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2858, + }, + { + stateMutability: "view", + type: "function", + name: "lastReport", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2888, + }, + { + stateMutability: "view", + type: "function", + name: "activation", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2918, + }, + { + stateMutability: "view", + type: "function", + name: "lockedProfit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2948, + }, + { + stateMutability: "view", + type: "function", + name: "lockedProfitDegration", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2978, + }, + { + stateMutability: "view", + type: "function", + name: "rewards", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 3008, + }, + { + stateMutability: "view", + type: "function", + name: "managementFee", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 3038, + }, + { + stateMutability: "view", + type: "function", + name: "performanceFee", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 3068, + }, + { + stateMutability: "view", + type: "function", + name: "nonces", + inputs: [{ name: "arg0", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + gas: 3313, + }, + { + stateMutability: "view", + type: "function", + name: "DOMAIN_SEPARATOR", + inputs: [], + outputs: [{ name: "", type: "bytes32" }], + gas: 3128, + }, +] + +export default YEARN_VAULT_ABI diff --git a/background/main.ts b/background/main.ts index 4dcf316d10..de04304b84 100644 --- a/background/main.ts +++ b/background/main.ts @@ -87,6 +87,7 @@ import { SignTypedDataRequest, SignDataRequest, } from "./utils/signing" +import { emitter as earnSliceEmitter } from "./redux-slices/earn" import { resetLedgerState, setDeviceConnectionStatus, @@ -671,6 +672,10 @@ export default class Main extends BaseService { ) }) + earnSliceEmitter.on("depositSuccessful", (message) => { + this.store.dispatch(setSnackbarMessage(message)) + }) + this.chainService.emitter.on("transactionSendFailure", () => { this.store.dispatch( setSnackbarMessage("Transaction failed to broadcast.") diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 479fe04b84..e64df4dc61 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -1,75 +1,304 @@ import { TransactionResponse } from "@ethersproject/abstract-provider" import { createSlice, createSelector } from "@reduxjs/toolkit" import { BigNumber, ethers } from "ethers" -import { HOUR } from "../constants" +import { parseUnits } from "ethers/lib/utils" +import Emittery from "emittery" + +import { AnyAsset } from "../assets" +import { USE_MAINNET_FORK } from "../features/features" import { ERC20_ABI } from "../lib/erc20" +import { fromFixedPointNumber } from "../lib/fixed-point" import VAULT_ABI from "../lib/vault" +import APPROVAL_TARGET_ABI from "../lib/approvalTarget" import { HexString } from "../types" import { createBackgroundAsyncThunk } from "./utils" import { getContract, + getCurrentTimestamp, getProvider, getSignerAddress, } from "./utils/contract-utils" +import { AssetsState, selectAssetPricePoint } from "./assets" +import { enrichAssetAmountWithMainCurrencyValues } from "./utils/asset-utils" export type ApprovalTargetAllowance = { contractAddress: HexString - allowance: string + allowance: number +} + +export type AvailableVault = { + vaultAddress: HexString + active: boolean + userDeposited: bigint + totalDeposited: bigint + yearnVault: HexString + asset: AnyAsset & { contractAddress: string; decimals: number } + pendingRewards: bigint } export type EarnState = { signature: Signature approvalTargetAllowances: ApprovalTargetAllowance[] + availableVaults: AvailableVault[] + currentlyDepositing: boolean + currentlyApproving: boolean + depositError: boolean + inputAmount: string + depositingProcess: boolean } export type Signature = { - r: string - s: string - v: number + r: string | undefined + s: string | undefined + v: number | undefined + deadline: number | undefined } +export type Events = { + depositSuccessful: string +} + +export const emitter = new Emittery() + export const initialState: EarnState = { signature: { - r: "", - s: "", - v: 0, + r: undefined, + s: undefined, + v: undefined, + deadline: undefined, }, approvalTargetAllowances: [], + availableVaults: [ + { + asset: { + name: "USDT", + symbol: "USDT", + contractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + decimals: 6, + }, + vaultAddress: "0x6575a8E8Ca0FD1Fb974419AE1f9128cCb1055209", + yearnVault: "0x7Da96a3891Add058AdA2E826306D812C638D87a7", + userDeposited: 0n, + totalDeposited: 0n, + pendingRewards: 0n, + active: true, + }, + { + asset: { + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + decimals: 8, + }, + vaultAddress: "0xAAfcDd71F8eb9B6229852fD6B005F0c39394Af06", + yearnVault: "0xA696a63cc78DfFa1a63E9E50587C197387FF6C7E", + userDeposited: 0n, + totalDeposited: 0n, + pendingRewards: 0n, + active: true, + }, + { + asset: { + name: "ChainLink", + symbol: "LINK", + contractAddress: "0x514910771AF9Ca656af840dff83E8264EcF986CA", + decimals: 18, + }, + vaultAddress: "0x30EEB5c3d3B3FB3aC532c77cD76dd59f78Ff9070", + yearnVault: "0x671a912C10bba0CFA74Cfc2d6Fba9BA1ed9530B2", + userDeposited: 0n, + totalDeposited: 0n, + pendingRewards: 0n, + active: false, + }, + ], + currentlyDepositing: false, + currentlyApproving: false, + depositError: false, + inputAmount: "", + depositingProcess: false, } -export type EIP712DomainType = { - name?: string - version?: string - chainId?: number - verifyingContract?: HexString -} +const APPROVAL_TARGET_CONTRACT_ADDRESS = + "0x76465982fD8070FC74c91FD4CFfC7eb56Fc6b03a" -export type PermitRequest = { - account: HexString - liquidityTokenAddress: HexString - liquidityAmount: BigNumber - nonce: BigNumber - deadline: BigNumber - spender: HexString -} +const earnSlice = createSlice({ + name: "earn", + initialState, + reducers: { + saveSignature: ( + state, + { payload: { r, s, v, deadline } }: { payload: Signature } + ) => ({ + ...state, + signature: { r, s, v, deadline }, + }), + clearSignature: (state) => ({ + ...state, + signature: { + r: undefined, + s: undefined, + v: undefined, + deadline: undefined, + }, + }), + clearInput: (state) => ({ + ...state, + inputAmount: "", + }), + currentlyDepositing: (immerState, { payload }: { payload: boolean }) => { + immerState.currentlyDepositing = payload + }, + depositProcess: (immerState, { payload }: { payload: boolean }) => { + immerState.depositingProcess = payload + }, + currentlyApproving: (immerState, { payload }: { payload: boolean }) => { + immerState.currentlyApproving = payload + }, + inputAmount: (state, { payload }: { payload: string }) => { + return { + ...state, + inputAmount: payload, + } + }, + earnedOnVault: ( + state, + { payload }: { payload: { vault: HexString; amount: bigint } } + ) => { + return { + ...state, + availableVaults: state.availableVaults.map((availableVault) => + availableVault.vaultAddress === payload.vault + ? { ...availableVault, pendingRewards: payload.amount } + : availableVault + ), + } + }, + lockedAmounts: ( + state, + { + payload, + }: { + payload: { + vault: AvailableVault + userLockedValue: bigint + totalTVL: bigint + } + } + ) => { + return { + ...state, + availableVaults: state.availableVaults.map((availableVault) => + availableVault.vaultAddress === payload.vault.vaultAddress + ? { + ...availableVault, + userDeposited: payload.userLockedValue, + totalDeposited: payload.totalTVL, + } + : availableVault + ), + } + }, + depositError: (immerState, { payload }: { payload: boolean }) => { + immerState.depositError = payload + }, + saveAllowance: ( + state, + { + payload, + }: { payload: { contractAddress: HexString; allowance: number } } + ) => { + const { contractAddress, allowance } = payload + return { + ...state, + approvalTargetAllowances: [ + ...state.approvalTargetAllowances, + { contractAddress, allowance }, + ], + } + }, + }, +}) -// once testnet contracts are deployed we should replace this -const APPROVAL_TARGET_CONTRACT_ADDRESS = - "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" // currently: swap router +export const { + saveSignature, + saveAllowance, + currentlyDepositing, + currentlyApproving, + earnedOnVault, + depositError, + lockedAmounts, + inputAmount, + clearSignature, + clearInput, + depositProcess, +} = earnSlice.actions + +export default earnSlice.reducer + +export const updateLockedValues = createBackgroundAsyncThunk( + "earn/updateLockedValues", + async (_, { getState, dispatch }) => { + const currentState = getState() + const { earn } = currentState as { earn: EarnState } + const { availableVaults } = earn + const provider = getProvider() + const signer = provider.getSigner() + const account = signer.getAddress() + + availableVaults.map(async (vault) => { + const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) + const userLockedValue: BigNumber = await vaultContract.balanceOf(account) + const yearnVaultContract = await getContract(vault.yearnVault, VAULT_ABI) + const totalTVL: BigNumber = await yearnVaultContract.balanceOf( + vault.vaultAddress + ) + dispatch( + lockedAmounts({ + vault, + userLockedValue: userLockedValue.toBigInt(), + totalTVL: totalTVL.toBigInt(), + }) + ) + return { + ...vault, + userDeposited: userLockedValue.toBigInt(), + totalDeposited: totalTVL.toBigInt(), + } + }) + } +) + +export const vaultWithdraw = createBackgroundAsyncThunk( + "earn/vaultWithdraw", + async ({ vault }: { vault: AvailableVault }, { dispatch }) => { + const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) + + // TODO Support partial withdrawal + // const withdrawAmount = parseUnits(amount, vault.asset.decimals) + + const tx = await vaultContract.functions["withdraw()"]() + const receipt = await tx.wait() + if (receipt.status === 1) { + dispatch(updateLockedValues()) + } + } +) export const vaultDeposit = createBackgroundAsyncThunk( - "signing/vaultAndDeposit", + "signing/vaultDeposit", async ( { - vaultContractAddress, + vault, amount, }: { - tokenContractAddress: HexString - vaultContractAddress: HexString - amount: BigInt + vault: AvailableVault + amount: string + tokenAddress: HexString }, - { getState } + { getState, dispatch } ) => { + dispatch(depositProcess(false)) const provider = getProvider() const signer = provider.getSigner() const signerAddress = await getSignerAddress() @@ -77,52 +306,66 @@ export const vaultDeposit = createBackgroundAsyncThunk( const state = getState() const { earn } = state as { earn: EarnState } - const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) + const { signature } = earn + + const { vaultAddress } = vault + + const depositAmount = parseUnits(amount, vault.asset.decimals) + + const vaultContract = await getContract(vaultAddress, VAULT_ABI) const depositTransactionData = await vaultContract.populateTransaction.depositWithApprovalTarget( - amount, + depositAmount, signerAddress, signerAddress, - amount, - (await provider.getBlock(provider.getBlockNumber())).timestamp + - 3 * HOUR, - earn.signature.r, - earn.signature.s, - earn.signature.v + depositAmount, + ethers.BigNumber.from(signature.deadline), + signature.v, + signature.r, + signature.s ) - signer.sendTransaction(depositTransactionData) + if (USE_MAINNET_FORK) { + depositTransactionData.gasLimit = BigNumber.from(850000) // for mainnet fork only + } + dispatch(clearInput()) + const response = await signer.sendTransaction(depositTransactionData) + dispatch(currentlyDepositing(true)) + const receipt = await response.wait() + if (receipt.status === 1) { + dispatch(currentlyDepositing(false)) + dispatch(clearSignature()) + dispatch(updateLockedValues()) + await emitter.emit("depositSuccessful", "Asset successfully deposited") + } + dispatch(clearSignature()) + dispatch(currentlyDepositing(false)) + dispatch(dispatch(depositError(true))) } ) -export const vaultWithdraw = createBackgroundAsyncThunk( - "earn/vaultWithdraw", - async ({ - vaultContractAddress, - amount, - }: { - vaultContractAddress: HexString - amount: BigNumber - }) => { +export const updateEarnedValues = createBackgroundAsyncThunk( + "earn/updateEarnedOnDepositedPools", + async (_, { getState, dispatch }) => { + const currentState = getState() + const { earn } = currentState as { earn: EarnState } + const { availableVaults } = earn const provider = getProvider() const signer = provider.getSigner() - - const vaultContract = new ethers.Contract( - vaultContractAddress, - VAULT_ABI, - signer - ) - const signedWithdrawTransaction = await signer.signTransaction( - await vaultContract.functions["withdraw(uint256)"](amount) - ) - - provider.sendTransaction(signedWithdrawTransaction) + const account = signer.getAddress() + availableVaults.forEach(async (vault) => { + const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) + const earned: BigNumber = await vaultContract.earned(account) + dispatch( + earnedOnVault({ vault: vault.vaultAddress, amount: earned.toBigInt() }) + ) + }) } ) -export const getRewards = createBackgroundAsyncThunk( - "earn/getRewards", - async (vaultContractAddress: HexString) => { +export const claimVaultRewards = createBackgroundAsyncThunk( + "earn/clamRewards", + async (vaultContractAddress: HexString, { dispatch }) => { const provider = getProvider() const signer = provider.getSigner() @@ -131,53 +374,20 @@ export const getRewards = createBackgroundAsyncThunk( VAULT_ABI, signer ) - const signedGetRewardsTx = await signer.signTransaction( - await vaultContract.functions.getReward() - ) - - provider.sendTransaction(signedGetRewardsTx) + const tx = await vaultContract.functions["getReward()"]() + const response = signer.sendTransaction(tx) + await tx.wait(response) + dispatch(updateEarnedValues()) } ) -const earnSlice = createSlice({ - name: "earn", - initialState, - reducers: { - saveSignature: ( - state, - { payload: { r, s, v } }: { payload: Signature } - ) => ({ - ...state, - signature: { r, s, v }, - }), - saveAllowance: ( - state, - { - payload, - }: { payload: { contractAddress: HexString; allowance: string } } - ) => { - const { contractAddress, allowance } = payload - return { - ...state, - approvalTargetAllowances: [ - ...state.approvalTargetAllowances, - { contractAddress, allowance }, - ], - } - }, - }, -}) - -export const { saveSignature, saveAllowance } = earnSlice.actions - -export default earnSlice.reducer - export const approveApprovalTarget = createBackgroundAsyncThunk( "earn/approveApprovalTarget", async ( tokenContractAddress: HexString, { dispatch } ): Promise => { + dispatch(currentlyApproving(true)) const provider = getProvider() const signer = provider.getSigner() @@ -189,19 +399,15 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( ethers.constants.MaxUint256 ) try { + if (USE_MAINNET_FORK) { + approvalTransactionData.gasLimit = BigNumber.from(350000) // for mainnet fork only + } const tx = await signer.sendTransaction(approvalTransactionData) await tx.wait() - - const { r, s, v } = tx - if ( - typeof r !== "undefined" && - typeof v !== "undefined" && - typeof s !== "undefined" - ) { - dispatch(earnSlice.actions.saveSignature({ r, s, v })) - } + dispatch(currentlyApproving(false)) return tx } catch (error) { + dispatch(currentlyApproving(false)) return undefined } } @@ -209,33 +415,25 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( export const checkApprovalTargetApproval = createBackgroundAsyncThunk( "earn/checkApprovalTargetApproval", - async (tokenContractAddress: HexString, { getState, dispatch }) => { - const currentState = getState() - const { earn } = currentState as { earn: EarnState } + async (tokenContractAddress: HexString, { dispatch }) => { const assetContract = await getContract(tokenContractAddress, ERC20_ABI) const signerAddress = await getSignerAddress() - - const knownAllowanceIndex = earn.approvalTargetAllowances.findIndex( - (allowance: ApprovalTargetAllowance) => - allowance.contractAddress === tokenContractAddress - ) - if (knownAllowanceIndex === -1) { - try { - const allowance: BigNumber = await assetContract.functions.allowance( - signerAddress, - APPROVAL_TARGET_CONTRACT_ADDRESS - ) - dispatch( - earnSlice.actions.saveAllowance({ - contractAddress: tokenContractAddress, - allowance: allowance.toString(), - }) - ) - } catch (err) { - return undefined - } + try { + const allowance: BigNumber = await assetContract.allowance( + signerAddress, + APPROVAL_TARGET_CONTRACT_ADDRESS + ) + const amount = fromFixedPointNumber( + { amount: allowance.toBigInt(), decimals: 18 }, + 2 + ) + return { + contractAddress: tokenContractAddress, + allowance: amount, + } as ApprovalTargetAllowance + } catch (err) { + return undefined } - return earn.approvalTargetAllowances[knownAllowanceIndex] } ) @@ -243,11 +441,13 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( "earn/permitVaultDeposit", async ( { - vaultContractAddress, + vault, amount, + tokenAddress, }: { - vaultContractAddress: HexString - amount: BigInt + vault: AvailableVault + amount: string + tokenAddress: HexString }, { dispatch } ) => { @@ -256,44 +456,40 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const signerAddress = await getSignerAddress() const chainID = await signer.getChainId() + const depositAmount = parseUnits(amount, vault.asset.decimals) + + const ApprovalTargetContract = await getContract( + APPROVAL_TARGET_CONTRACT_ADDRESS, + APPROVAL_TARGET_ABI + ) + + const timestamp = await getCurrentTimestamp() + const deadline = timestamp + 12 * 60 * 60 + + const nonceValue = await ApprovalTargetContract.nonces(signerAddress) const types = { - Message: [ - { - name: "owner", - type: "address", - }, - { - name: "spender", - type: "address", - }, - { - name: "value", - type: "string", - }, - { - name: "nonce", - type: "uint256", - }, - { - name: "deadline", - type: "uint256", - }, + PermitAndTransferFrom: [ + { name: "erc20", type: "address" }, + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, ], } const domain = { - name: "Spend assets with ApprovalTarget", + name: "ApprovalTarget", + chainId: USE_MAINNET_FORK ? 1337 : chainID, version: "1", - verifyingContract: vaultContractAddress, - chainId: chainID, + verifyingContract: APPROVAL_TARGET_CONTRACT_ADDRESS, } const message = { + erc20: tokenAddress, owner: signerAddress, - spender: vaultContractAddress, - value: amount.toString(), - nonce: 0, - deadline: - (await provider.getBlock(provider.getBlockNumber())).timestamp + - 3 * HOUR, + spender: vault.vaultAddress, + value: depositAmount, + nonce: nonceValue, + deadline: ethers.BigNumber.from(deadline), } // _signTypedData is the ethers function name, once the official release will be ready _ will be dropped @@ -303,7 +499,8 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const splitSignature = ethers.utils.splitSignature(tx) const { r, s, v } = splitSignature - dispatch(earnSlice.actions.saveSignature({ r, s, v })) + dispatch(earnSlice.actions.saveSignature({ r, s, v, deadline })) + dispatch(depositProcess(true)) } ) export const selectApprovalTargetApprovals = createSelector( @@ -315,3 +512,80 @@ export const selectApprovalTargetApprovals = createSelector( }, (approvals) => approvals ) + +export const selectCurrentlyApproving = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState?.currentlyApproving +) + +export const selectCurrentlyDepositing = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.currentlyDepositing +) + +export const selectAvailableVaults = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.availableVaults +) + +export const selectSignature = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => { + if ( + typeof earnState.signature.r !== "undefined" && + typeof earnState.signature.v !== "undefined" && + typeof earnState.signature.s !== "undefined" && + typeof earnState.signature.deadline !== "undefined" + ) { + return earnState.signature + } + return undefined + } +) + +export const selectEarnInputAmount = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.inputAmount +) + +export const selectDepositingProcess = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.depositingProcess +) + +export const selectEnrichedAvailableVaults = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (state: { assets: AssetsState }): AssetsState => state.assets, + (earnState: EarnState, assetsState: AssetsState) => { + // FIXME make this proper main currency + const mainCurrencySymbol = "USD" + const vaultsWithMainCurrencyValues = earnState.availableVaults.map( + (vault) => { + const assetPricePoint = selectAssetPricePoint( + assetsState, + vault.asset.symbol, + mainCurrencySymbol + ) + const userTVL = enrichAssetAmountWithMainCurrencyValues( + { amount: vault.userDeposited, asset: vault.asset }, + assetPricePoint, + 2 + ) + const totalTVL = enrichAssetAmountWithMainCurrencyValues( + { amount: vault.totalDeposited, asset: vault.asset }, + assetPricePoint, + 2 + ) + + return { + ...vault, + localValueUserDeposited: userTVL.localizedMainCurrencyAmount, + localValueTotalDeposited: totalTVL.localizedMainCurrencyAmount, + numberValueUserDeposited: userTVL.mainCurrencyAmount, + numberValueTotalDeposited: totalTVL.mainCurrencyAmount, + } + } + ) + return vaultsWithMainCurrencyValues + } +) diff --git a/background/redux-slices/index.ts b/background/redux-slices/index.ts index 0e9111ec5b..f2fd1a851d 100644 --- a/background/redux-slices/index.ts +++ b/background/redux-slices/index.ts @@ -29,7 +29,7 @@ const mainReducer = combineReducers({ claim: claimReducer, signing: signingReducer, ...(HIDE_IMPORT_LEDGER ? {} : { ledger: ledgerReducer }), - ...(HIDE_EARN_PAGE ? {} : { earn: earnReducer }), + earn: earnReducer, }) export default mainReducer diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index e9740a8b26..2c2040dfbb 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -19,8 +19,7 @@ import logger from "../../lib/logger" import { EVMNetwork, SmartContract } from "../../networks" import { getBalance, getMetadata as getERC20Metadata } from "../../lib/erc20" import { USE_MAINNET_FORK } from "../../features/features" -import { ETHEREUM } from "../../constants" -import { DOGGO_TOKEN_ADDRESS } from "../../redux-slices/claim" +import { FORK } from "../../constants" interface ProviderManager { providerForNetwork(network: EVMNetwork): SerialFallbackProvider | undefined @@ -67,9 +66,15 @@ export default class AssetDataHelper { error ) } + // Load balances of tokens on the mainnet fork if (USE_MAINNET_FORK) { - const tokens = [DOGGO_TOKEN_ADDRESS] + const tokens = [ + "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", // WBTC + "0x514910771AF9Ca656af840dff83E8264EcF986CA", // LINK + "0xC8B1e49A5dDE816BCde63F23e7E787086229FE62", // DOGGO + ] const balances = tokens.map(async (token) => { const balance = await getBalance( provider, @@ -79,7 +84,7 @@ export default class AssetDataHelper { return { smartContract: { contractAddress: token, - homeNetwork: ETHEREUM, + homeNetwork: FORK, }, amount: BigInt(balance.toString()), } diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 688478adea..88b41539b8 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -45,6 +45,7 @@ import type { } from "../enrichment" import { HOUR } from "../../constants" import SerialFallbackProvider from "./serial-fallback-provider" +import { USE_MAINNET_FORK } from "../../features/features" import AssetDataHelper from "./asset-data-helper" // We can't use destructuring because webpack has to replace all instances of @@ -599,6 +600,9 @@ export default class ChainService extends BaseService { network: EVMNetwork, transactionRequest: EIP1559TransactionRequest ): Promise { + if (USE_MAINNET_FORK) { + return 350000n + } const estimate = await this.providers.ethereum.estimateGas( ethersTransactionRequestFromEIP1559TransactionRequest(transactionRequest) ) diff --git a/background/services/internal-ethereum-provider/index.ts b/background/services/internal-ethereum-provider/index.ts index 9e77dc2851..8b254505a1 100644 --- a/background/services/internal-ethereum-provider/index.ts +++ b/background/services/internal-ethereum-provider/index.ts @@ -122,6 +122,7 @@ export default class InternalEthereumProviderService extends BaseService )}` case "eth_blockNumber": case "eth_call": + case "eth_estimateGas": case "eth_feeHistory": case "eth_gasPrice": case "eth_getBalance": @@ -194,7 +195,6 @@ export default class InternalEthereumProviderService extends BaseService case "metamask_sendDomainMetadata": case "wallet_requestPermissions": case "wallet_watchAsset": - case "eth_estimateGas": case "estimateGas": // --- eip1193-bridge only method -- case "eth_coinbase": // --- MM only methods --- case "eth_decrypt": diff --git a/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx b/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx index cedb03ce7b..159856431d 100644 --- a/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx +++ b/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx @@ -14,6 +14,7 @@ import { normalizeEVMAddress, sameEVMAddress, } from "@tallyho/tally-background/lib/utils" +import { clearSignature } from "@tallyho/tally-background/redux-slices/earn" import { resetClaimFlow } from "@tallyho/tally-background/redux-slices/claim" import SharedButton from "../Shared/SharedButton" import { @@ -157,6 +158,7 @@ export default function AccountsNotificationPanelAccounts({ useBackgroundSelector(selectCurrentAccount).address const updateCurrentAccount = (address: string) => { + dispatch(clearSignature()) setPendingSelectedAddress(address) dispatch( setNewSelectedAccount({ diff --git a/ui/components/Earn/EarnDepositedCard.tsx b/ui/components/Earn/EarnDepositedCard.tsx index 3544e45d48..555ef52ed4 100644 --- a/ui/components/Earn/EarnDepositedCard.tsx +++ b/ui/components/Earn/EarnDepositedCard.tsx @@ -8,17 +8,19 @@ export default function EarnDepositedCard({ asset, depositedAmount, availableRewards, + vaultAddress, }: { asset: (AnyAsset & { contractAddress: HexString }) | undefined depositedAmount: number availableRewards: number + vaultAddress: HexString }): ReactElement { return (
  • Deposited amount - {depositedAmount} + ${depositedAmount}
  • Available rewards @@ -47,11 +49,12 @@ export default function EarnDepositedCard({ width: 352px; height: 176px; border-radius: 8px; - background-color: var(--green-95); + background: linear-gradient(var(--green-95) 100%, var(--green-95)); box-sizing: border-box; padding: 16px; margin-bottom: 20px; margin-top: 30px; + transition: all 0.2s ease; } .card:hover { box-shadow: 0px 10px 12px 0px #0014138a; diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index d161fb43c3..e230068aac 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -1,39 +1,52 @@ -import React, { ReactElement, useState } from "react" -import classNames from "classnames" -import { AnyAsset } from "@tallyho/tally-background/assets" -import { HexString } from "@tallyho/tally-background/types" +import { + AvailableVault, + selectEnrichedAvailableVaults, + updateEarnedValues, + updateLockedValues, +} from "@tallyho/tally-background/redux-slices/earn" +import { formatCurrencyAmount } from "@tallyho/tally-background/redux-slices/utils/asset-utils" +import { selectMainCurrencySymbol } from "@tallyho/tally-background/redux-slices/selectors" +import { doggoTokenDecimalDigits } from "@tallyho/tally-background/constants" +import { fromFixedPointNumber } from "@tallyho/tally-background/lib/fixed-point" + +import React, { ReactElement, useEffect, useState } from "react" import { Link } from "react-router-dom" +import classNames from "classnames" import SharedAssetIcon from "../components/Shared/SharedAssetIcon" import SharedPanelSwitcher from "../components/Shared/SharedPanelSwitcher" import EarnDepositedCard from "../components/Earn/EarnDepositedCard" +import { useBackgroundDispatch, useBackgroundSelector } from "../hooks" -type AssetProp = { - asset: (AnyAsset & { contractAddress: HexString }) | undefined +type EarnCardProps = { + vault: AvailableVault & { + localValueTotalDeposited: string | undefined + localValueUserDeposited: string | undefined + } isComingSoon: boolean } -function EarnCard({ asset, isComingSoon }: AssetProp) { +function EarnCard({ vault, isComingSoon }: EarnCardProps) { return (
    - +
    - {asset?.symbol} + {vault?.asset?.symbol} Estimated APR 250%
    TVL
    -
    $22.800.322
    +
    ${vault.localValueTotalDeposited ?? 0}
    @@ -150,20 +163,45 @@ function EarnCard({ asset, isComingSoon }: AssetProp) { export default function Earn(): ReactElement { const [panelNumber, setPanelNumber] = useState(0) - const assets = [ - { - name: "Dai Token", - symbol: "DAI", - contractAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", - } as AnyAsset & { contractAddress: HexString }, - { - name: "Keep", - symbol: "KEEP", - contractAddress: "0x85eee30c52b0b379b046fb0f85f4f3dc3009afec", - } as AnyAsset & { contractAddress: HexString }, - ] + const dispatch = useBackgroundDispatch() + + const vaultsWithMainCurrencyValues = useBackgroundSelector( + selectEnrichedAvailableVaults + ) + + const mainCurrencySymbol = useBackgroundSelector(selectMainCurrencySymbol) + + const isComingSoon = false + + useEffect(() => { + dispatch(updateLockedValues()) + dispatch(updateEarnedValues()) + }, [dispatch]) + + const totalTVL = vaultsWithMainCurrencyValues + .map((item) => { + return typeof item.numberValueTotalDeposited !== "undefined" + ? item.numberValueTotalDeposited + : 0 + }) + .reduce((prev, curr) => prev + curr, 0) + + const userTVL = vaultsWithMainCurrencyValues + .map((item) => { + return typeof item.numberValueUserDeposited !== "undefined" + ? item.numberValueUserDeposited + : 0 + }) + .reduce((prev, curr) => prev + curr, 0) - const isComingSoon = true + const userPendingRewards = vaultsWithMainCurrencyValues + .map((item) => { + return fromFixedPointNumber( + { amount: item.pendingRewards, decimals: doggoTokenDecimalDigits }, + 2 + ) + }) + .reduce((prev, curr) => prev + curr, 0) return ( <> @@ -188,7 +226,8 @@ export default function Earn(): ReactElement {
    Total value locked
    - $23,928,292 + $ + {formatCurrencyAmount(mainCurrencySymbol, totalTVL, 2)}
    @@ -202,9 +241,9 @@ export default function Earn(): ReactElement { {panelNumber === 0 ? (
      - {assets.map((asset) => ( + {vaultsWithMainCurrencyValues?.map((vault) => (
    • - +
    • ))}
    @@ -226,20 +265,29 @@ export default function Earn(): ReactElement {
    Total deposits
    -
    $134,928
    +
    + ${formatCurrencyAmount(mainCurrencySymbol, userTVL, 2)} +
    Total available rewards
    -
    ~$12,328
    +
    {userPendingRewards} DOGGO
      - {assets.map((asset) => ( + {vaultsWithMainCurrencyValues.map((vault) => (
    • ))} diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 8270a374b2..8bef6b3b99 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -1,15 +1,37 @@ import React, { ReactElement, useEffect, useState } from "react" -import { selectAccountAndTimestampedActivities } from "@tallyho/tally-background/redux-slices/selectors" import { + selectCurrentAccount, + selectCurrentAccountBalances, +} from "@tallyho/tally-background/redux-slices/selectors" +import { + ApprovalTargetAllowance, approveApprovalTarget, checkApprovalTargetApproval, + claimVaultRewards, + clearSignature, + inputAmount, permitVaultDeposit, - selectApprovalTargetApprovals, + selectCurrentlyApproving, + selectCurrentlyDepositing, + selectDepositingProcess, + selectEarnInputAmount, + selectEnrichedAvailableVaults, + selectSignature, + updateEarnedValues, + updateLockedValues, + vaultDeposit, + vaultWithdraw, } from "@tallyho/tally-background/redux-slices/earn" - -import { AnyAsset } from "@tallyho/tally-background/assets" +import { + clearTransactionState, + TransactionConstructionStatus, +} from "@tallyho/tally-background/redux-slices/transaction-construction" +import { fromFixedPointNumber } from "@tallyho/tally-background/lib/fixed-point" +import { doggoTokenDecimalDigits } from "@tallyho/tally-background/constants" import { HexString } from "@tallyho/tally-background/types" -import { useLocation } from "react-router-dom" +import { getCurrentTimestamp } from "@tallyho/tally-background/redux-slices/utils/contract-utils" + +import { useHistory, useLocation } from "react-router-dom" import BackButton from "../components/Shared/SharedBackButton" import SharedAssetIcon from "../components/Shared/SharedAssetIcon" @@ -20,73 +42,181 @@ import SharedSlideUpMenu from "../components/Shared/SharedSlideUpMenu" import { useBackgroundDispatch, useBackgroundSelector } from "../hooks" export default function EarnDeposit(): ReactElement { + const storedInput = useBackgroundSelector(selectEarnInputAmount) const [panelNumber, setPanelNumber] = useState(0) - const [amount, setAmount] = useState("") + const [amount, setAmount] = useState(storedInput) const [hasError, setHasError] = useState(false) const [withdrawSlideupVisible, setWithdrawalSlideupVisible] = useState(false) const [isApproved, setIsApproved] = useState(false) const [deposited, setDeposited] = useState(false) - const [availableRewards, setAvailableRewards] = useState("21,832") const dispatch = useBackgroundDispatch() - const { asset } = useLocation().state as { - asset: AnyAsset & { contractAddress: HexString } - } + const history = useHistory() - const showWithdrawalModal = () => { - setWithdrawalSlideupVisible(true) + const { vaultAddress } = useLocation().state as { + vaultAddress: HexString } - const { combinedData } = useBackgroundSelector( - selectAccountAndTimestampedActivities + const isCurrentlyApproving = useBackgroundSelector(selectCurrentlyApproving) + const signature = useBackgroundSelector(selectSignature) + const inDepositProcess = useBackgroundSelector(selectDepositingProcess) + const isDepositPending = useBackgroundSelector(selectCurrentlyDepositing) + + const enrichedVaults = useBackgroundSelector(selectEnrichedAvailableVaults) + const account = useBackgroundSelector(selectCurrentAccount) + + const vault = enrichedVaults.find( + (enrichedVault) => enrichedVault?.vaultAddress === vaultAddress ) - // We currently assume that the approval held by ApprovalTarget is infinite and users wont decrease it themselves. - const approvals = useBackgroundSelector(selectApprovalTargetApprovals) + const accountBalances = useBackgroundSelector(selectCurrentAccountBalances) - const isTokenApproved = () => { - if (!isApproved) { - const allowanceIndex = approvals?.findIndex( - (approval) => approval.contractAddress === asset.contractAddress - ) - if (allowanceIndex !== -1) { - setIsApproved(true) + useEffect(() => { + if (typeof vault?.asset?.contractAddress !== "undefined") { + const checkApproval = async () => { + const getApprovalAmount = async () => { + const approvedAmount = (await dispatch( + checkApprovalTargetApproval(vault.asset.contractAddress) + )) as unknown as ApprovalTargetAllowance + return approvedAmount.allowance + } + const allowance = await getApprovalAmount() + const allowanceGreaterThanAmount = allowance >= Number(amount) + setIsApproved(allowanceGreaterThanAmount) } + checkApproval() } + }, [ + amount, + dispatch, + vault?.asset?.contractAddress, + account.address, + isCurrentlyApproving, + ]) + + useEffect(() => { + const checkCurrentSignatureDeadline = async () => { + const timestamp = await getCurrentTimestamp() + if ( + typeof signature?.deadline !== "undefined" && + timestamp > signature.deadline + ) { + dispatch(clearSignature()) + } + } + checkCurrentSignatureDeadline() + }, [dispatch, signature?.deadline]) + + useEffect(() => { + dispatch(updateLockedValues()) + dispatch(updateEarnedValues()) + return () => { + dispatch(clearSignature()) + } + }, [dispatch, account.address]) + + useEffect(() => { + if (inDepositProcess && typeof vault !== "undefined") { + dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) + dispatch( + vaultDeposit({ + vault, + amount, + tokenAddress: vault.asset.contractAddress, + }) + ) + history.push("/sign-transaction") + } + }, [amount, dispatch, history, inDepositProcess, vault]) + + if (typeof vault === "undefined") { + return <> + } + + const pendingRewards = fromFixedPointNumber( + { amount: vault.pendingRewards, decimals: doggoTokenDecimalDigits }, + 2 + ) + + const userDeposited = fromFixedPointNumber( + { amount: vault.userDeposited, decimals: vault.asset.decimals }, + 4 + ) + + if ( + typeof vault.numberValueUserDeposited !== "undefined" && + vault.numberValueUserDeposited > 0 && + deposited === false + ) { + setDeposited(true) + } else if ( + typeof vault.numberValueUserDeposited !== "undefined" && + vault.numberValueUserDeposited === 0 && + deposited === true + ) { + setDeposited(false) } - isTokenApproved() + const showWithdrawalModal = () => { + setWithdrawalSlideupVisible(true) + } - const approve = () => { - dispatch(approveApprovalTarget(asset.contractAddress)) + const approve = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) + dispatch(approveApprovalTarget(vault.asset.contractAddress)) + history.push("/sign-transaction") } - const enable = () => { + const deposit = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) dispatch( permitVaultDeposit({ - vaultContractAddress: asset.contractAddress, - amount: 2000n, + vault, + tokenAddress: vault.asset.contractAddress, + amount, }) ) + history.push("/sign-data") } - const deposit = () => { - setDeposited(true) - } - - const withdraw = () => { + const withdraw = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) + dispatch( + vaultWithdraw({ + vault, + }) + ) setDeposited(false) setWithdrawalSlideupVisible(false) + history.push("/sign-transaction") } - const claimRewards = () => { - setAvailableRewards("0") + const claimRewards = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) + dispatch(claimVaultRewards(vault.vaultAddress)) + history.push("/sign-transaction") } - useEffect(() => { - dispatch(checkApprovalTargetApproval(asset.contractAddress)) - }, [dispatch, asset.contractAddress]) + const handleAmountChange = ( + value: string, + errorMessage: string | undefined + ) => { + setAmount(value) + dispatch(inputAmount(value)) + if (errorMessage) { + setHasError(true) + } else { + setHasError(false) + } + } + + const approveButtonText = () => { + if (isCurrentlyApproving === true) { + return "Approving..." + } + return "Approve asset" + } return ( <> @@ -96,8 +226,8 @@ export default function EarnDeposit(): ReactElement {
    • VAULT
      - -

      {asset.symbol}

      + +

      {vault?.asset.symbol}

    • Total value locked
      -
      $20,283,219
      +
      ${vault.localValueTotalDeposited}
    • Rewards
      @@ -124,19 +254,20 @@ export default function EarnDeposit(): ReactElement {
  • - {deposited ? ( + {deposited || pendingRewards > 0 ? (
  • Deposited amount
    - 27,834 {asset.symbol} + {userDeposited} + {vault?.asset.symbol}
  • Available rewards
    - {availableRewards} DOGGO + {pendingRewards} DOGGO
  • @@ -158,34 +289,37 @@ export default function EarnDeposit(): ReactElement { {panelNumber === 0 ? (
    { - setAmount(value) - if (errorMessage) { - setHasError(true) - } else { - setHasError(false) - } + onAmountChange={(value, errorMessage) => + handleAmountChange(value, errorMessage) + } + selectedAsset={{ + name: vault.asset.name, + symbol: vault.asset.symbol, + contractAddress: vault.asset.contractAddress, }} - selectedAsset={asset} amount={amount} disableDropdown />
    - {!isApproved ? ( + {!isApproved || isCurrentlyApproving ? ( - {!isApproved ? "Approve" : "Enable"} + {approveButtonText()} ) : ( - - {!deposited ? "Deposit" : "Deposit more"} + + {isDepositPending ? "Depositing..." : "Authorize & Deposit"} )}
    @@ -234,14 +368,15 @@ export default function EarnDeposit(): ReactElement {
  • Deposited amount
    - 27,834 Curve ibGBP + {userDeposited} + {vault.asset.symbol}
  • Available rewards
    - {availableRewards} DOGGO + {pendingRewards} DOGGO
  • @@ -328,6 +463,7 @@ export default function EarnDeposit(): ReactElement { background-color: var(--trophy-gold); } .token { + margin-left: 8px; font-size: 14px; } .divider { diff --git a/ui/pages/SignData.tsx b/ui/pages/SignData.tsx index e7c32525ad..cc986a5298 100644 --- a/ui/pages/SignData.tsx +++ b/ui/pages/SignData.tsx @@ -65,13 +65,20 @@ export default function SignData(): ReactElement { history.goBack() } + const getTitle = () => { + if (typedDataRequest.typedData.primaryType === "PermitAndTransferFrom") { + return "Authorize Deposit" + } + return `Sign ${typedDataRequest.typedData.primaryType ?? "Message"}` + } + return ( } reviewPanel={} isTransactionSigning={isTransactionSigning} diff --git a/ui/routes/routes.tsx b/ui/routes/routes.tsx index 395a29eaef..face7b155e 100644 --- a/ui/routes/routes.tsx +++ b/ui/routes/routes.tsx @@ -1,4 +1,5 @@ import React, { ReactElement } from "react" +import { EARN_COMING_SOON } from "@tallyho/tally-background/features/features" import Wallet from "../pages/Wallet" import SignTransaction from "../pages/SignTransaction" import SignData from "../pages/SignData" @@ -121,7 +122,7 @@ const pageList: PageList[] = [ }, { path: "/earn", - Component: ComingSoon ?? Earn, + Component: EARN_COMING_SOON ? ComingSoon : Earn, hasTabBar: true, hasTopBar: true, persistOnClose: true,