From 900c8972338c67f2562fae88f07fecf0529cdb09 Mon Sep 17 00:00:00 2001 From: Tempe Techie <95053628+tempe-techie@users.noreply.github.com> Date: Sat, 14 Sep 2024 21:01:23 +0200 Subject: [PATCH] arweave address is shown in case balance drops below threshold --- .env.example | 1 + README.md | 6 + components/chat/ChatFeed.vue | 2 +- .../collection/AddImageToCollectionModal.vue | 2 +- .../ChangeCollectionPreviewModal.vue | 2 +- components/profile/PunkProfile.vue | 2 +- components/storage/FileUploadInput.vue | 5 +- components/storage/FileUploadModal.vue | 134 ++++++++++++------ layouts/default.vue | 14 +- nuxt.config.ts | 3 + pages/nft/create.vue | 2 +- store/site.ts | 69 +++++---- utils/balanceUtils.js | 77 +++++----- utils/textUtils.js | 94 ++++++------ 14 files changed, 245 insertions(+), 168 deletions(-) diff --git a/.env.example b/.env.example index d5784ec..4202d9d 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +ARWEAVE_ADDRESS= ARWEAVE_KTY= ARWEAVE_N= ARWEAVE_E= diff --git a/README.md b/README.md index 1f4fb15..05cc95b 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,12 @@ ARWEAVE_DQ= ARWEAVE_QI= ``` +And add the arweave address too: + +```bash +ARWEAVE_ADDRESS= +``` + Also make sure these variables are set on your hosting provider (Netlify, Vercel, etc). ## Image upload fallback diff --git a/components/chat/ChatFeed.vue b/components/chat/ChatFeed.vue index e3914ac..61ad968 100644 --- a/components/chat/ChatFeed.vue +++ b/components/chat/ChatFeed.vue @@ -44,7 +44,7 @@ @processFileUrl="insertImage" title="Upload image" infoText="Upload an image." - storageType="arweave" + :storageType="$config.fileUploadStorageType" :componentId="$.uid" :maxFileSize="$config.fileUploadSizeLimit" /> diff --git a/components/nft/collection/AddImageToCollectionModal.vue b/components/nft/collection/AddImageToCollectionModal.vue index 551c81d..90dcbe7 100644 --- a/components/nft/collection/AddImageToCollectionModal.vue +++ b/components/nft/collection/AddImageToCollectionModal.vue @@ -15,7 +15,7 @@ diff --git a/components/nft/collection/ChangeCollectionPreviewModal.vue b/components/nft/collection/ChangeCollectionPreviewModal.vue index 24763c6..84bfb5e 100644 --- a/components/nft/collection/ChangeCollectionPreviewModal.vue +++ b/components/nft/collection/ChangeCollectionPreviewModal.vue @@ -17,7 +17,7 @@ diff --git a/components/profile/PunkProfile.vue b/components/profile/PunkProfile.vue index 063a153..a81af26 100644 --- a/components/profile/PunkProfile.vue +++ b/components/profile/PunkProfile.vue @@ -205,7 +205,7 @@ @processFileUrl="insertImage" title="Change profile image" infoText="Upload a new profile picture." - storageType="arweave" + :storageType="$config.fileUploadStorageType" :componentId="$.uid" :maxFileSize="$config.fileUploadSizeLimit" /> diff --git a/components/storage/FileUploadInput.vue b/components/storage/FileUploadInput.vue index 1e03d99..2dfff54 100644 --- a/components/storage/FileUploadInput.vue +++ b/components/storage/FileUploadInput.vue @@ -4,9 +4,10 @@ type="file" class="form-control form-control-lg mb-3" :id="'file-input-' + componentId" + :disabled="waitingUpload || disable" /> - @@ -22,7 +23,7 @@ import ImageKit from 'imagekit-javascript' export default { name: 'FileUploadInput', - props: ['btnCls', 'maxFileSize', 'storageType'], + props: ['btnCls', 'disable', 'maxFileSize', 'storageType'], emits: ['processUploadedFileUrl'], data() { diff --git a/components/storage/FileUploadModal.vue b/components/storage/FileUploadModal.vue index 789aa5d..18b63fc 100644 --- a/components/storage/FileUploadModal.vue +++ b/components/storage/FileUploadModal.vue @@ -1,48 +1,75 @@ \ No newline at end of file + diff --git a/layouts/default.vue b/layouts/default.vue index 56215d4..2e625b7 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -122,7 +122,7 @@ import NavbarMobile from "~/components/navbars/NavbarMobile.vue"; import SidebarLeft from "~/components/sidebars/SidebarLeft.vue"; import SidebarRight from "~/components/sidebars/SidebarRight.vue"; import ChatSettingsModal from "~/components/ChatSettingsModal.vue"; -import { getActivityPoints } from '~/utils/balanceUtils'; +import { getActivityPoints, getArweaveBalance } from '~/utils/balanceUtils'; import { getAltDomainName, getDomainHolder, getDomainName } from '~/utils/domainUtils'; import { storeAltname, storeReferrer, storeUsername } from '~/utils/storageUtils'; import VerifyAccountOwnership from '~/components/VerifyAccountOwnership.vue'; @@ -193,6 +193,9 @@ export default { selector: "[data-bs-toggle='popover']", }) + // fetch Arweave balance + this.fetchArweaveBalance() + // check if file upload is enabled this.siteStore.setFileUploadEnabled(this.$config.fileUploadEnabled); @@ -255,6 +258,15 @@ export default { } }, + async fetchArweaveBalance() { + if (this.$config.arweaveAddress) { + const balance = await getArweaveBalance(this.$config.arweaveAddress) + //console.log('Arweave balance:', balance) + + this.siteStore.setArweaveBalance(balance) + } + }, + async fetchChatTokenBalance() { if (this.$config.chatTokenAddress) { const chatTokenInterface = new ethers.utils.Interface([ diff --git a/nuxt.config.ts b/nuxt.config.ts index 7b7f3cd..28f76a9 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -54,7 +54,9 @@ export default defineNuxtConfig({ airdropApAddress: "", // chat token claim for APs airdropClaimDomainsAddress: "", // chat token claim for domain holders altDomain: ".mode", // alternative domain (e.g. .eth, .crypto, etc.) + arweaveAddress: process.env.ARWEAVE_ADDRESS, arweaveGateway: 'https://arweave.net/', + arweaveMinBalance: 0.02, // minimum AR balance to upload files blockExplorerBaseUrl: "https://explorer.mode.network", // no trailing slash! chatChannels: { // go to Orbis Dashboard (https://useorbis.com/dashboard), create a new Project and then create a new Context for each of the channels below "general": "kjzl6cwe1jw149v2ugvw3kux2bllw3bka0vl2rzgmdkaceg3srm62nww7iuvkwi", // general discussion channel @@ -72,6 +74,7 @@ export default defineNuxtConfig({ favicon: "/img/favicon.svg", fileUploadEnabled: true, // enable/disable file uploads (enable only if external file storage is used, e.g. Arweave) fileUploadSizeLimit: 1 * 1024 * 1024, // max file upload size in bytes (1 * 1024 * 1024 = 1 MB) + fileUploadStorageType: "arweave", // "imagekit" or "arweave" (or leave empty for no file uploads) fileUploadTokenService: process.env.FILE_UPLOAD_SERVICE || "netlify", // "netlify" or "vercel" (or leave empty for no file uploads) getPostsLimit: 30, // number of posts to fetch from Orbis in the getPosts() function governanceUrl: "", // governance url (snapshot, Tally, etc.) diff --git a/pages/nft/create.vue b/pages/nft/create.vue index f4253fc..deed6ef 100644 --- a/pages/nft/create.vue +++ b/pages/nft/create.vue @@ -160,7 +160,7 @@ @processFileUrl="insertImage" title="Upload your NFT image" infoText="Upload the NFT image." - storageType="arweave" + :storageType="$config.fileUploadStorageType" :componentId="$.uid" :maxFileSize="$config.fileUploadSizeLimit" /> diff --git a/store/site.ts b/store/site.ts index f32bfdd..6258462 100644 --- a/store/site.ts +++ b/store/site.ts @@ -6,83 +6,92 @@ export const useSiteStore = defineStore({ state: () => { return { - colorMode: "dark", + arweaveBalance: 0, + colorMode: 'dark', fileUploadEnabled: true, - showOnlyMasterPosts: useLocalStorage('showOnlyMasterPosts', "false"), - slippage: "0.5", // percentage (%) - swapDeadline: "20" // minutes + showOnlyMasterPosts: useLocalStorage('showOnlyMasterPosts', 'false'), + slippage: '0.5', // percentage (%) + swapDeadline: '20', // minutes } }, getters: { + getArweaveBalance(state) { + return state.arweaveBalance + }, + getColorMode(state) { - const pStorage = useLocalStorage('colorMode', null); + const pStorage = useLocalStorage('colorMode', null) if (pStorage.value) { - state.colorMode = pStorage.value; + state.colorMode = pStorage.value } - return state.colorMode; + return state.colorMode }, getFileUploadEnabled(state) { - return state.fileUploadEnabled; + return state.fileUploadEnabled }, getShowOnlyMasterPosts(state) { - const pStorage = useLocalStorage('showOnlyMasterPosts', null); + const pStorage = useLocalStorage('showOnlyMasterPosts', null) if (pStorage.value) { - state.showOnlyMasterPosts = pStorage.value; + state.showOnlyMasterPosts = pStorage.value } - return state.showOnlyMasterPosts; + return state.showOnlyMasterPosts }, getSlippage(state) { - const pStorage = useLocalStorage('swapSlippage', "0.5"); + const pStorage = useLocalStorage('swapSlippage', '0.5') if (pStorage.value) { - state.slippage = pStorage.value; + state.slippage = pStorage.value } - return state.slippage; + return state.slippage }, getSwapDeadline(state) { - const pStorage = useLocalStorage('swapDeadline', "20"); + const pStorage = useLocalStorage('swapDeadline', '20') if (pStorage.value) { - state.swapDeadline = pStorage.value; + state.swapDeadline = pStorage.value } - return state.swapDeadline; - } + return state.swapDeadline + }, }, actions: { + setArweaveBalance(balance: number) { + this.arweaveBalance = balance + }, + setColorMode(cm: string) { - this.colorMode = cm; - localStorage.setItem("colorMode", cm); + this.colorMode = cm + localStorage.setItem('colorMode', cm) }, setFileUploadEnabled(enabled: boolean) { - this.fileUploadEnabled = enabled; + this.fileUploadEnabled = enabled }, setShowOnlyMasterPosts(somp: string) { - this.showOnlyMasterPosts = somp; - localStorage.setItem("showOnlyMasterPosts", somp); + this.showOnlyMasterPosts = somp + localStorage.setItem('showOnlyMasterPosts', somp) }, setSlippage(slippage: string) { - this.slippage = slippage; - localStorage.setItem("swapSlippage", slippage); + this.slippage = slippage + localStorage.setItem('swapSlippage', slippage) }, setSwapDeadline(swapDeadline: string) { - this.swapDeadline = swapDeadline; - localStorage.setItem("swapDeadline", swapDeadline); - } - } -}) \ No newline at end of file + this.swapDeadline = swapDeadline + localStorage.setItem('swapDeadline', swapDeadline) + }, + }, +}) diff --git a/utils/balanceUtils.js b/utils/balanceUtils.js index bdd6062..0b430fe 100644 --- a/utils/balanceUtils.js +++ b/utils/balanceUtils.js @@ -1,70 +1,67 @@ -import { ethers } from "ethers"; -import Erc20Abi from "~/assets/abi/Erc20Abi.json"; +import Arweave from 'arweave' +import { ethers } from 'ethers' +import Erc20Abi from '~/assets/abi/Erc20Abi.json' + +// Initialize Arweave +const arweave = Arweave.init({ + host: 'arweave.net', + port: 443, + protocol: 'https' +}) export async function getActivityPoints(userAddress, signer) { - const config = useRuntimeConfig(); - - let provider = signer; + const config = useRuntimeConfig() - if (!signer) { - provider = this.$getFallbackProvider(config.supportedChainId); - } + let provider = signer const activityPointsInterface = new ethers.utils.Interface([ - "function getPoints(address user_) external view returns (uint256)", - ]); + 'function getPoints(address user_) external view returns (uint256)', + ]) - const activityPointsContract = new ethers.Contract(config.activityPointsAddress, activityPointsInterface, provider); + const activityPointsContract = new ethers.Contract(config.activityPointsAddress, activityPointsInterface, provider) - const pointsWei = await activityPointsContract.getPoints(userAddress); + const pointsWei = await activityPointsContract.getPoints(userAddress) - let activityPoints = Number(ethers.utils.formatEther(pointsWei)); + let activityPoints = Number(ethers.utils.formatEther(pointsWei)) if (activityPoints < 1) { - activityPoints = activityPoints.toFixed(2); + activityPoints = activityPoints.toFixed(2) } else { - activityPoints = Math.round(activityPoints); + activityPoints = Math.round(activityPoints) } - return activityPoints; + return activityPoints } -export async function getTokenAllowance(token, userAddress, beneficiary, signer) { - const config = useRuntimeConfig(); - - let provider = signer; +export async function getArweaveBalance(arweaveAddress) { + const balance = await arweave.wallets.getBalance(arweaveAddress) + return arweave.ar.winstonToAr(balance) +} - if (!provider) { - provider = this.$getFallbackProvider(config.supportedChainId); - } +export async function getTokenAllowance(token, userAddress, beneficiary, signer) { + let provider = signer - const contract = new ethers.Contract(token.address, Erc20Abi, provider); - const allowanceWei = await contract.allowance(userAddress, beneficiary); + const contract = new ethers.Contract(token.address, Erc20Abi, provider) + const allowanceWei = await contract.allowance(userAddress, beneficiary) - return ethers.utils.formatUnits(allowanceWei, token.decimals); + return ethers.utils.formatUnits(allowanceWei, token.decimals) } export async function getTokenBalance(token, userAddress, signer) { - const config = useRuntimeConfig(); - - let provider = signer; - - if (!provider) { - provider = this.$getFallbackProvider(config.supportedChainId); - } + let provider = signer - let balanceWei; + let balanceWei if (token.address === ethers.constants.AddressZero) { if (!signer) { - balanceWei = await provider.getBalance(userAddress); + balanceWei = await provider.getBalance(userAddress) } else { - balanceWei = await signer.getBalance(); + balanceWei = await signer.getBalance() } } else { - const contract = new ethers.Contract(token.address, Erc20Abi, provider); - balanceWei = await contract.balanceOf(userAddress); + const contract = new ethers.Contract(token.address, Erc20Abi, provider) + balanceWei = await contract.balanceOf(userAddress) } - return ethers.utils.formatUnits(balanceWei, token.decimals); -} \ No newline at end of file + return ethers.utils.formatUnits(balanceWei, token.decimals) +} diff --git a/utils/textUtils.js b/utils/textUtils.js index 4a1a65b..676e0fe 100644 --- a/utils/textUtils.js +++ b/utils/textUtils.js @@ -1,6 +1,3 @@ -//import { isBlank, zeroWidthCharacters } from 'printable-characters'; -import { getWorkingUrl } from '~/utils/ipfsUtils' - export function findFirstCollectionUrl(text) { // if there is an NFT collection url (from our website) in the text, return it as address string // example: @@ -13,12 +10,12 @@ export function findFirstCollectionUrl(text) { try { urlRegex = new RegExp( - '(https?:\\/\\/(?!.*\\.(jpg|png|jpeg|img|gif|webp|pdf|docx))[^\\s]+)(?' - } - } - + return text.replace(imageRegex, function (url) { + let newUrl = url if (url.startsWith('ar://')) { - url = url.replace('ar://', 'https://arweave.net/') + newUrl = url.replace('ar://', 'https://arweave.net/') + } else if (url.startsWith('ipfs://')) { + newUrl = url.replace('ipfs://', 'https://ipfs.io/ipfs/') } - return '
' + return '
' }) } @@ -437,6 +438,8 @@ export function imgWithoutExtensionParsing(text) { return text.replace(imageRegex, function (url) { if (url.startsWith('ar://')) { url = url.replace('ar://', 'https://arweave.net/') + } else if (url.startsWith('ipfs://')) { + url = url.replace('ipfs://', 'https://ipfs.io/ipfs/') } return '' @@ -457,12 +460,12 @@ export function urlParsing(text) { try { urlRegex = new RegExp( - '(https?:\\/\\/(?!.*\\.(jpg|png|jpeg|img|gif|webp|pdf|docx))[^\\s]+)(?