-
{{ asset.name }}
+
{{ asset?.name }}
{{
- $formatNumber(fromWeiWithDecimals(asset.amount, asset.decimals))
+ $formatNumber(
+ fromWeiWithDecimals(asset.balance, asset.decimals)
+ )
}}
0
{{ asset.symbol }}{{ asset?.symbol }}
- {{ $formatCurrency(asset.amount, asset.symbol) }}
+ {{ $formatCurrency(asset.balance, asset.symbol) }}
-const { profile: connectedProfile, status } = useConnectedProfileStore()
-const { profile: viewedProfile } = useViewedProfileStore()
-const appStore = useAppStore()
+const { connectedProfile } = useConnectedProfile()
+const { currentNetwork, isConnected } = storeToRefs(useAppStore())
+const { viewedProfile } = useViewedProfile()
const contentRef = ref()
const logoRef = ref()
const symbolRef = ref()
@@ -10,8 +10,8 @@ const balanceWidthPx = ref(0)
const handleSendAsset = (event: Event) => {
try {
event.stopPropagation()
- assertAddress(connectedProfile.address, 'profile')
- navigateTo(sendRoute(connectedProfile.address))
+ assertAddress(connectedProfile.value?.address, 'profile')
+ navigateTo(sendRoute(connectedProfile.value.address))
} catch (error) {
console.error(error)
}
@@ -19,8 +19,8 @@ const handleSendAsset = (event: Event) => {
const handleShowLyxDetails = () => {
try {
- assertAddress(viewedProfile.address, 'profile')
- navigateTo(lyxDetailsRoute(viewedProfile.address))
+ assertAddress(viewedProfile.value?.address, 'profile')
+ navigateTo(lyxDetailsRoute(viewedProfile.value.address))
} catch (error) {
console.error(error)
}
@@ -60,7 +60,7 @@ onMounted(async () => {
LUKSO
{{ appStore.currentNetwork.token.symbol }}{{ currentNetwork.token.symbol }}
{{
$formatCurrency(
- viewedProfile.balance,
+ viewedProfile?.balance || '',
CURRENCY_API_LYX_TOKEN_NAME
)
}}
@@ -96,8 +96,7 @@ onMounted(async () => {
{
+ const assetRepo = useRepo(AssetRepository)
+ const asset = ref()
+ const { isLoadingAssets, isLoadedApp } = storeToRefs(useAppStore())
+ const { viewedProfile } = useViewedProfile()
+
+ watchEffect(async () => {
+ if (!assetAddress || !isLoadedApp.value) {
+ return
+ }
+
+ let storeAsset = assetRepo.getAssetAndImages(assetAddress, tokenId)
+
+ if (!storeAsset) {
+ let fetchedAsset: Asset[]
+ isLoadingAssets.value = true
+
+ if (tokenId) {
+ fetchedAsset = await fetchAsset(
+ assetAddress,
+ viewedProfile.value?.address,
+ [tokenId]
+ )
+ } else {
+ fetchedAsset = await fetchAsset(
+ assetAddress,
+ viewedProfile.value?.address
+ )
+ }
+
+ fetchedAsset && fetchedAsset.length && assetRepo.saveAssets(fetchedAsset)
+ storeAsset = assetRepo.getAssetAndImages(assetAddress, tokenId)
+
+ try {
+ } catch (error) {
+ console.error(error)
+ } finally {
+ isLoadingAssets.value = false
+ }
+ }
+
+ asset.value = storeAsset
+ })
+
+ return {
+ asset,
+ }
+}
diff --git a/composables/useBrowserExtension.ts b/composables/useBrowserExtension.ts
index 58efb613..c5be4550 100644
--- a/composables/useBrowserExtension.ts
+++ b/composables/useBrowserExtension.ts
@@ -1,3 +1,5 @@
+import { ProviderAPI } from '@/types/provider'
+
const openStoreLink = () => {
const storeLink = browserInfo().storeLink
@@ -14,8 +16,7 @@ const setConnectionExpiry = () => {
const connect = async () => {
const { showModal } = useModal()
const { formatMessage } = useIntl()
- const { setStatus, reloadProfile: reloadConnectedProfile } =
- useConnectedProfileStore()
+ const { connectedProfileAddress, isConnecting } = storeToRefs(useAppStore())
// when no extension installed we show modal
if (!INJECTED_PROVIDER) {
@@ -27,7 +28,7 @@ const connect = async () => {
})
}
- setStatus('isConnecting', true)
+ isConnecting.value = true
try {
const { accounts, requestAccounts } = useWeb3(PROVIDERS.INJECTED)
@@ -42,15 +43,13 @@ const connect = async () => {
}
assertAddress(address, 'connection')
- setItem(STORAGE_KEY.CONNECTED_ADDRESS, address)
- const profile = await fetchProfile(address)
- await loadViewedProfile(address)
- await loadViewedAssets(address)
- reloadConnectedProfile(profile)
- setStatus('isConnected', true)
- setConnectionExpiry()
+ connectedProfileAddress.value = address
+ // TODO try to refresh current page based on router params
await navigateTo(profileRoute(address))
- } catch (error: unknown) {
+ await fetchProfile(address)
+ await fetchAssets(address)
+ setConnectionExpiry()
+ } catch (error: any) {
console.error(error)
disconnect()
@@ -59,25 +58,24 @@ const connect = async () => {
message: getConnectionErrorMessage(error),
})
} finally {
- setStatus('isConnecting', false)
+ isConnecting.value = false
}
}
const disconnect = () => {
- const { setStatus } = useConnectedProfileStore()
const { removeItem } = useLocalStorage()
+ const { connectedProfileAddress } = storeToRefs(useAppStore())
- setStatus('isConnected', false)
- removeItem(STORAGE_KEY.CONNECTED_ADDRESS)
+ connectedProfileAddress.value = undefined
removeItem(STORAGE_KEY.CONNECTION_EXPIRY)
removeItem(STORAGE_KEY.RECONNECT_ADDRESS)
}
const handleAccountsChanged = async (accounts: string[]) => {
- const { status } = useConnectedProfileStore()
+ const { connectedProfileAddress, isConnected } = storeToRefs(useAppStore())
// handle account change only for connected users
- if (!status.isConnected) {
+ if (!isConnected.value) {
return
}
@@ -86,14 +84,15 @@ const handleAccountsChanged = async (accounts: string[]) => {
assertAddress(address, 'profile')
// if user is already connected we need to update Local Storage key
- if (status.isConnected) {
- setItem(STORAGE_KEY.CONNECTED_ADDRESS, address)
+ if (isConnected.value) {
+ connectedProfileAddress.value = address
}
try {
+ // TODO try to refresh current page based on router params
await navigateTo(profileRoute(address))
- await loadViewedProfile(address)
- await loadViewedAssets(address)
+ await fetchProfile(address)
+ await fetchAssets(address)
} catch (error) {
console.error(error)
}
@@ -103,18 +102,28 @@ const handleAccountsChanged = async (accounts: string[]) => {
}
}
+const handleChainChanged = (network: { chainId: string }) => {
+ const { selectedChainId } = storeToRefs(useAppStore())
+
+ selectedChainId.value = network.chainId
+ disconnect()
+ navigateTo(homeRoute())
+}
+
const handleDisconnect = () => {
location.reload()
}
-const addProviderEvents = async (provider: any) => {
+const addProviderEvents = async (provider: ProviderAPI) => {
provider?.on?.('accountsChanged', handleAccountsChanged)
provider?.on?.('disconnect', handleDisconnect)
+ provider?.on?.('chainChanged', handleChainChanged)
}
-const removeProviderEvents = async (provider: any) => {
- provider?.removeListener?.('accountsChanged', handleAccountsChanged)
- provider?.removeListener?.('disconnect', handleAccountsChanged)
+const removeProviderEvents = async (provider: ProviderAPI) => {
+ provider?.off?.('accountsChanged', handleAccountsChanged)
+ provider?.off?.('disconnect', handleDisconnect)
+ provider?.off?.('chainChanged', handleChainChanged)
}
const isUniversalProfileExtension = () => {
diff --git a/composables/useConnectedProfile.ts b/composables/useConnectedProfile.ts
new file mode 100644
index 00000000..68405303
--- /dev/null
+++ b/composables/useConnectedProfile.ts
@@ -0,0 +1,23 @@
+import { ProfileRepository } from '@/repositories/profile'
+import { Profile } from '@/models/profile'
+
+export const useConnectedProfile = () => {
+ const profileRepo = useRepo(ProfileRepository)
+ const { connectedProfileAddress } = storeToRefs(useAppStore())
+ const connectedProfile = ref()
+
+ watchEffect(async () => {
+ if (!connectedProfileAddress.value) {
+ return
+ }
+
+ const storeProfile = profileRepo.getProfileAndImages(
+ connectedProfileAddress.value
+ )
+ connectedProfile.value = storeProfile
+ })
+
+ return {
+ connectedProfile,
+ }
+}
diff --git a/composables/useErc725.ts b/composables/useErc725.ts
index 94b31686..9c6119f3 100644
--- a/composables/useErc725.ts
+++ b/composables/useErc725.ts
@@ -1,16 +1,5 @@
import { ERC725, ERC725JSONSchema } from '@erc725/erc725.js'
-import LSP3ProfileMetadata from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json'
-import { LSP3Profile } from '@lukso/lsp-factory.js'
import Web3 from 'web3'
-import { LSP4DigitalAssetJSON } from '@lukso/lsp-factory.js/build/main/src/lib/interfaces/lsp4-digital-asset'
-
-import { Lsp8TokenIdType } from '@/types/assets'
-import LSP8IdentifiableDigitalAsset from '@/shared/schemas/LSP8IdentifiableDigitalAsset.json'
-import { Profile } from '@/types/profile'
-
-export interface LSP3ProfileJSON {
- LSP3Profile: LSP3Profile
-}
const getInstance = (address: string, schema: ERC725JSONSchema[]) => {
const { currentNetwork } = useAppStore()
@@ -23,134 +12,9 @@ const getInstance = (address: string, schema: ERC725JSONSchema[]) => {
return erc725
}
-const fetchProfile = async (profileAddress: Address): Promise => {
- const erc725 = getInstance(
- profileAddress,
- LSP3ProfileMetadata as ERC725JSONSchema[]
- )
- const profileMetadata = await erc725.fetchData('LSP3Profile')
- const lsp3Profile = validateLsp3Metadata(profileMetadata)
- const profileImage =
- lsp3Profile.profileImage &&
- (await getAndConvertImage(lsp3Profile.profileImage, 200))
- const backgroundImage =
- lsp3Profile.backgroundImage &&
- (await getAndConvertImage(lsp3Profile.backgroundImage, 800))
-
- const { getBalance } = useWeb3(PROVIDERS.RPC)
- const balance = await getBalance(profileAddress)
-
- return {
- ...lsp3Profile,
- address: profileAddress,
- profileImage,
- backgroundImage,
- balance,
- metadata: lsp3Profile,
- }
-}
-
-const fetchAssets = async (profileAddress: Address, schema: string) => {
- const erc725 = getInstance(
- profileAddress,
- LSP3ProfileMetadata as ERC725JSONSchema[]
- )
- const result = await erc725.fetchData(schema)
- const assetAddresses = result.value as Address[]
- const { profile } = useViewedProfileStore()
-
- const assets = await Promise.all(
- assetAddresses.map(async address => {
- const standard = await detectStandard(address)
-
- switch (standard) {
- case 'LSP8IdentifiableDigitalAsset': {
- assertAddress(profile.address)
- return await fetchLsp8Assets(address, profile.address)
- }
- case 'LSP7DigitalAsset': {
- assertAddress(profile.address)
- return await fetchLsp7Assets(address, profile.address)
- }
-
- default:
- return []
- }
- })
- )
-
- return assets.flat()
-}
-
-const fetchLsp8Metadata = async (
- tokenId: string,
- assetAddress: Address
-): Promise => {
- const lsp8MetadataGetter = async (
- tokenIdType: string,
- tokenId: string
- ): Promise => {
- const lsp8Metadata = await erc725.fetchData([
- {
- keyName: `LSP8MetadataJSON:<${tokenIdType}>`,
- dynamicKeyParts: tokenId,
- },
- ])
- return validateLSP4MetaData(lsp8Metadata[0].value)
- }
-
- const erc725 = getInstance(
- assetAddress,
- LSP8IdentifiableDigitalAsset as ERC725JSONSchema[]
- )
-
- try {
- const lsp8DigitalAsset = await erc725.fetchData(['LSP8TokenIdType'])
- const tokenIdType = lsp8DigitalAsset[0].value.toString()
-
- // fetch LSP8MetadataJSON depending on tokenIdType
- switch (tokenIdType) {
- case Lsp8TokenIdType.address:
- return lsp8MetadataGetter(
- 'address',
- // ethers.utils.hexDataSlice(tokenId.toString(), 12)
- tokenId.toString()
- )
- case Lsp8TokenIdType.number:
- return lsp8MetadataGetter('uint256', parseInt(tokenId).toString())
- case Lsp8TokenIdType.bytes32:
- return lsp8MetadataGetter('bytes32', tokenId.toString())
- default:
- return {
- LSP4Metadata: {
- description: '',
- links: [],
- images: [[]],
- icon: [],
- assets: [],
- },
- }
- }
- } catch (error) {
- console.error(error)
- return {
- LSP4Metadata: {
- description: '',
- links: [],
- images: [[]],
- icon: [],
- assets: [],
- },
- }
- }
-}
-
const useErc725 = () => {
return {
getInstance,
- fetchProfile,
- fetchAssets,
- fetchLsp8Metadata,
}
}
diff --git a/composables/useProfile.ts b/composables/useProfile.ts
new file mode 100644
index 00000000..563361f0
--- /dev/null
+++ b/composables/useProfile.ts
@@ -0,0 +1,20 @@
+import { Profile } from '@/models/profile'
+import { ProfileRepository } from '@/repositories/profile'
+
+export const useProfile = (profileAddress?: Address) => {
+ const profileRepo = useRepo(ProfileRepository)
+ const profile = ref()
+
+ watchEffect(async () => {
+ if (!profileAddress) {
+ return
+ }
+
+ const storeProfile = profileRepo.getProfileAndImages(profileAddress)
+ profile.value = storeProfile
+ })
+
+ return {
+ profile,
+ }
+}
diff --git a/composables/useProfileBase.ts b/composables/useProfileBase.ts
deleted file mode 100644
index 3d12e9f4..00000000
--- a/composables/useProfileBase.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { toChecksumAddress } from 'web3-utils'
-
-import { Profile } from '@/types/profile'
-
-/**
- * Set of methods shared across profile stores
- *
- * @param profile - Profile object
- * @returns - methods
- */
-export const useProfileBase = (profile: Profile) => {
- return {
- setProfile(newProfile: Profile) {
- assertAddress(newProfile.address, 'profile')
- // we need to make conversion since ext uses checksum addresses
- const checksumAddress = toChecksumAddress(newProfile.address)
- assertAddress(checksumAddress, 'profile')
- newProfile.address = checksumAddress
- Object.assign(profile, newProfile)
- },
-
- clearProfile() {
- Object.assign(profile, {})
- },
-
- reloadProfile(newProfile: Profile) {
- this.clearProfile()
- this.setProfile(newProfile)
- },
- }
-}
diff --git a/composables/useViewedProfile.ts b/composables/useViewedProfile.ts
new file mode 100644
index 00000000..b03be947
--- /dev/null
+++ b/composables/useViewedProfile.ts
@@ -0,0 +1,21 @@
+import { Profile } from '@/models/profile'
+import { ProfileRepository } from '@/repositories/profile'
+
+export const useViewedProfile = () => {
+ const profileRepo = useRepo(ProfileRepository)
+ const viewedProfileAddress = getCurrentProfileAddress()
+ const viewedProfile = ref()
+
+ watchEffect(async () => {
+ if (!viewedProfileAddress) {
+ return
+ }
+
+ const storeProfile = profileRepo.getProfileAndImages(viewedProfileAddress)
+ viewedProfile.value = storeProfile
+ })
+
+ return {
+ viewedProfile,
+ }
+}
diff --git a/composables/useWeb3.ts b/composables/useWeb3.ts
index 73b03d7d..d82d2276 100644
--- a/composables/useWeb3.ts
+++ b/composables/useWeb3.ts
@@ -62,5 +62,8 @@ export default function useWeb3(providerName: string) {
console.log(JSON.stringify(payload, null, 2))
})
},
+ getChainId: async () => {
+ return await getWeb3().eth.getChainId()
+ },
}
}
diff --git a/models/asset.ts b/models/asset.ts
new file mode 100644
index 00000000..57de7dcf
--- /dev/null
+++ b/models/asset.ts
@@ -0,0 +1,62 @@
+import { Item } from 'pinia-orm'
+import { LinkMetadata } from '@lukso/lsp-smart-contracts'
+
+import { Profile } from '@/models/profile'
+import { InterfaceId } from '@/types/assets'
+import { Image } from '@/models/image'
+import { BaseModel } from '@/models/base'
+
+export class AssetModel extends BaseModel {
+ static entity = 'assets'
+ static primaryKey = ['address', 'tokenId']
+
+ static fields() {
+ return {
+ ...super.fields(),
+ address: this.attr(null),
+ name: this.string(''),
+ symbol: this.string(''),
+ balance: this.string(''),
+ decimals: this.number(0),
+ tokenSupply: this.string(''),
+ standard: this.string(''),
+ description: this.string(''),
+ links: this.attr([]),
+ tokenId: this.string(''),
+ isNativeToken: this.boolean(false),
+
+ // foreign keys
+ iconId: this.attr(null),
+ imageIds: this.attr(null),
+ creatorIds: this.attr(null),
+ }
+ }
+
+ // types
+ declare address: Address
+ declare name?: string
+ declare symbol?: string
+ declare balance?: string
+ declare decimals?: number
+ declare tokenSupply?: string
+ declare standard?: InterfaceId
+ declare description?: string
+ declare links?: LinkMetadata[]
+ declare tokenId?: string
+ declare isNativeToken?: boolean
+
+ declare iconId?: string
+ declare imageIds?: string[]
+
+ declare icon?: Image
+ declare images?: Image[]
+ declare creators?: Profile[]
+
+ static piniaOptions = {
+ persist: {
+ key: STORAGE_KEY.ASSET_STORE,
+ },
+ }
+}
+
+export type Asset = Partial- >
diff --git a/models/base.ts b/models/base.ts
new file mode 100644
index 00000000..1cf35e62
--- /dev/null
+++ b/models/base.ts
@@ -0,0 +1,16 @@
+import { Model } from 'pinia-orm'
+
+export class BaseModel extends Model {
+ static fields() {
+ return {
+ chainId: this.attr(null),
+ }
+ }
+
+ declare chainId?: string
+
+ static creating(model: Model) {
+ const { selectedChainId } = storeToRefs(useAppStore())
+ model.chainId = selectedChainId.value
+ }
+}
diff --git a/models/creator.ts b/models/creator.ts
new file mode 100644
index 00000000..0dd0005e
--- /dev/null
+++ b/models/creator.ts
@@ -0,0 +1,40 @@
+import { Item } from 'pinia-orm'
+
+import { Profile } from '@/models/profile'
+import { Asset } from '@/models/asset'
+import { BaseModel } from '@/models/base'
+
+export class CreatorModel extends BaseModel {
+ static entity = 'creators'
+ static primaryKey = ['profileId', 'assetId', 'tokenId']
+
+ static fields() {
+ return {
+ ...super.fields(),
+ isVerified: this.attr(false),
+
+ // foreign keys
+ profileId: this.attr(null),
+ assetId: this.attr(null),
+ tokenId: this.attr(null),
+ }
+ }
+
+ // types
+ declare isVerified?: boolean
+
+ declare profileId?: Address
+ declare assetId?: Address
+ declare tokenId?: string
+
+ declare profile?: Profile
+ declare asset?: Asset
+
+ static piniaOptions = {
+ persist: {
+ key: STORAGE_KEY.CREATOR_STORE,
+ },
+ }
+}
+
+export type Creator = Partial
- >
diff --git a/models/image.ts b/models/image.ts
new file mode 100644
index 00000000..0460c1fb
--- /dev/null
+++ b/models/image.ts
@@ -0,0 +1,36 @@
+import { Item } from 'pinia-orm'
+
+import { BaseModel } from '@/models/base'
+
+export class ImageModel extends BaseModel {
+ static entity = 'images'
+ static primaryKey = 'hash'
+
+ static fields() {
+ return {
+ ...super.fields(),
+ width: this.number(0),
+ height: this.number(0),
+ hashFunction: this.string(''),
+ hash: this.string(''),
+ url: this.string(''),
+ base64: this.string(''),
+ }
+ }
+
+ // types
+ declare hash: string
+ declare width?: number
+ declare height?: number
+ declare hashFunction?: string
+ declare url?: string
+ declare base64?: Base64EncodedImage
+
+ static piniaOptions = {
+ persist: {
+ key: STORAGE_KEY.IMAGE_STORE,
+ },
+ }
+}
+
+export type Image = Partial
- >
diff --git a/models/profile.ts b/models/profile.ts
new file mode 100644
index 00000000..8bd32620
--- /dev/null
+++ b/models/profile.ts
@@ -0,0 +1,71 @@
+import { Item } from 'pinia-orm'
+import { LSP3ProfileMetadata, LinkMetadata } from '@lukso/lsp-smart-contracts'
+
+import { Image } from '@/models/image'
+import { BaseModel } from '@/models/base'
+
+export class ProfileModel extends BaseModel {
+ static entity = 'profiles'
+ static primaryKey = 'address'
+
+ static fields() {
+ return {
+ ...super.fields(),
+ address: this.attr(null),
+ name: this.string(''),
+ balance: this.string(''),
+ links: this.attr([]),
+ tags: this.attr([]),
+ description: this.string(''),
+ isEoa: this.boolean(false),
+
+ // foreign keys
+ profileImageId: this.attr(null),
+ backgroundImageId: this.attr(null),
+ issuedAssetIds: this.attr(null),
+ receivedAssetIds: this.attr(null),
+ }
+ }
+
+ // types
+ declare address?: Address
+ declare name?: string
+ declare balance?: string
+ declare links?: LinkMetadata[]
+ declare tags?: string[]
+ declare description?: string
+ declare isEoa?: boolean
+
+ declare profileImageId?: string
+ declare backgroundImageId?: string
+ declare issuedAssetIds?: Address[]
+ declare receivedAssetIds?: Address[]
+
+ declare profileImage?: Image
+ declare backgroundImage?: Image
+
+ static piniaOptions = {
+ persist: {
+ key: STORAGE_KEY.PROFILE_STORE,
+ },
+ }
+}
+
+export type Profile = Partial
- >
+
+export type IndexedProfile = {
+ address: Address
+ profileURL?: string
+ profileHash?: string
+ profileHashFunction?: string
+ LSP3Profile?: LSP3ProfileMetadata
+ hasProfileName?: boolean
+ hasProfileDescription?: boolean
+ backgroundImageUrl?: string
+ hasBackgroundImage?: boolean
+ profileImageUrl?: string
+ hasProfileImage?: boolean
+ updatedAtBlock: number
+ network: string
+ objectID: string
+}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 94b127ec..2eff9fcb 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -23,6 +23,7 @@ export default defineNuxtConfig({
'@nuxtjs/device',
'@nuxtjs/plausible',
'@nuxtjs/algolia',
+ '@pinia-orm/nuxt',
],
plausible: {
domain: 'wallet.universalprofile.cloud',
@@ -98,7 +99,7 @@ export default defineNuxtConfig({
},
},
imports: {
- dirs: ['stores/**', 'shared/**'],
+ dirs: ['stores/**', 'shared/**', 'models/**'],
presets: [
{
from: 'pinia',
diff --git a/package.json b/package.json
index c31d676a..90c68f72 100644
--- a/package.json
+++ b/package.json
@@ -28,17 +28,17 @@
"typechain": "typechain --target web3-v1 --out-dir types/contracts/ './node_modules/@lukso/lsp-smart-contracts/artifacts/*.json'"
},
"devDependencies": {
- "@erc725/erc725.js": "0.19.0",
+ "@erc725/erc725.js": "0.20.0",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@formatjs/intl": "^2.9.0",
- "@lukso/lsp-factory.js": "3.1.1",
- "@lukso/lsp-smart-contracts": "0.11.1",
+ "@lukso/lsp-smart-contracts": "0.12.0-rc.0",
"@lukso/web-components": "1.47.1",
"@nuxt/devtools": "^0.7.4",
"@nuxtjs/algolia": "^1.9.0",
"@nuxtjs/device": "^3.1.0",
"@nuxtjs/plausible": "^0.2.1",
"@nuxtjs/tailwindcss": "^6.8.0",
+ "@pinia-orm/nuxt": "^1.7.0",
"@pinia-plugin-persistedstate/nuxt": "^1.1.2",
"@pinia/nuxt": "^0.4.11",
"@playwright/test": "1.36.2",
@@ -68,6 +68,7 @@
"npm-run-all": "4.1.5",
"nuxt": "^3.6.5",
"pinia": "^2.1.7",
+ "pinia-orm": "^1.7.2",
"postcss-html": "^1.5.0",
"prettier": "3.0.1",
"rollup-plugin-polyfill-node": "^0.12.0",
@@ -81,7 +82,7 @@
"stylelint-prettier": "4.0.2",
"stylelint-scss": "5.0.1",
"typechain": "^8.3.2",
- "typescript": "5.1.6",
+ "typescript": "5.2.2",
"util": "0.12.5",
"web3": "~1.10.0",
"web3-utils": "~1.10.0",
diff --git a/pages/[profileAddress]/index.vue b/pages/[profileAddress]/index.vue
index 187d64b0..134d42b3 100644
--- a/pages/[profileAddress]/index.vue
+++ b/pages/[profileAddress]/index.vue
@@ -1,30 +1,54 @@
@@ -54,11 +85,11 @@ const showProfileDetails = computed(
-
+
@@ -92,12 +123,12 @@ const showProfileDetails = computed(
>
-
-
+
+
-
+
diff --git a/pages/[profileAddress]/lyx-details.vue b/pages/[profileAddress]/lyx-details.vue
index 9f76892e..b33ed3b4 100644
--- a/pages/[profileAddress]/lyx-details.vue
+++ b/pages/[profileAddress]/lyx-details.vue
@@ -1,11 +1,10 @@
-
-
-
-
-
-
-
-
-
-
-
{{
- $formatMessage('token_details_send', {
- token: nft?.symbol || '',
- })
- }}
-
-
-
-
- {{ nft?.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pages/[profileAddress]/send.vue b/pages/[profileAddress]/send.vue
index 6e2986cc..8b09f1fe 100644
--- a/pages/[profileAddress]/send.vue
+++ b/pages/[profileAddress]/send.vue
@@ -8,20 +8,17 @@ import {
LSP7DigitalAsset,
LSP8IdentifiableDigitalAsset,
} from '@/types/contracts'
+import { AssetRepository } from '@/repositories/asset'
-const { profile: connectedProfile, status } = useConnectedProfileStore()
-const {
- profile: viewedProfile,
- setBalance,
- removeNft,
-} = useViewedProfileStore()
-const { ownedAssets } = storeToRefs(useViewedProfileStore())
+const { connectedProfile } = useConnectedProfile()
const { currentNetwork } = useAppStore()
const { asset, onSend, amount, receiver } = storeToRefs(useSendStore())
+const { isLoadedApp, isConnected } = storeToRefs(useAppStore())
const { setStatus, clearSend } = useSendStore()
const { showModal } = useModal()
const { formatMessage } = useIntl()
const { sendTransaction, getBalance, contract } = useWeb3(PROVIDERS.INJECTED)
+const assetRepository = useRepo(AssetRepository)
onMounted(() => {
setStatus('draft')
@@ -41,36 +38,43 @@ onUnmounted(() => {
watchEffect(() => {
// until everything is loaded we skip this effect
- if (!status.isProfileLoaded) {
+ if (!isLoadedApp) {
return
}
try {
amount.value = undefined
const assetAddress = useRouter().currentRoute.value.query.asset
+ const tokenId = useRouter().currentRoute.value.query.tokenId
assertAddress(assetAddress, 'asset')
- asset.value = ownedAssets.value?.find(
- asset => asset.address === assetAddress
- )
- assertAddress(asset.value?.address, 'asset')
+ const storeAsset = assetRepository.getAssetAndImages(assetAddress, tokenId)
+
+ if (!storeAsset) {
+ assertAddress(connectedProfile.value?.address, 'profile')
+ navigateTo(sendRoute(connectedProfile.value?.address))
+ } else {
+ asset.value = storeAsset
+ }
} catch (error) {
// fallback to native token
asset.value = {
name: currentNetwork.token.name,
symbol: currentNetwork.token.symbol,
- icon: ASSET_LYX_ICON_URL,
isNativeToken: true,
+ decimals: ASSET_LYX_DECIMALS,
}
}
// since balance is not avail in onMounted hook
asset.value = {
...asset.value,
- amount: isLyx(asset.value) ? connectedProfile.balance : asset.value.amount,
+ balance: isLyx(asset.value)
+ ? connectedProfile.value?.balance
+ : asset.value?.balance,
}
// when logout
- if (!status.isConnected) {
+ if (!isConnected) {
navigateTo(homeRoute())
}
})
@@ -82,7 +86,7 @@ const handleSend = async () => {
// native token transfer
if (isLyx(asset.value)) {
const transaction = {
- from: connectedProfile.address,
+ from: connectedProfile.value?.address,
to: receiver.value?.address as unknown as string,
value: toWei(amount.value || '0'),
} as TransactionConfig
@@ -98,22 +102,23 @@ const handleSend = async () => {
asset.value?.address
)
- assertAddress(connectedProfile.address)
+ assertAddress(connectedProfile.value?.address)
assertAddress(receiver.value?.address)
await tokenContract.methods
.transfer(
- connectedProfile.address,
+ connectedProfile.value.address,
receiver.value?.address,
toWei(amount.value || '0'),
false,
'0x'
)
- .send({ from: connectedProfile.address })
+ .send({ from: connectedProfile.value.address })
const balance = (await tokenContract.methods
- .balanceOf(connectedProfile.address)
+ .balanceOf(connectedProfile.value.address)
.call()) as string
assertAddress(asset.value?.address, 'asset')
- setBalance(asset.value.address, balance)
+ assetRepository.setBalance(asset.value.address, balance)
+
break
case 'LSP8IdentifiableDigitalAsset':
const nftContract = contract(
@@ -121,20 +126,20 @@ const handleSend = async () => {
asset.value?.address
)
- assertAddress(connectedProfile.address)
+ assertAddress(connectedProfile.value?.address)
assertAddress(receiver.value?.address)
assertString(asset.value.tokenId)
await nftContract.methods
.transfer(
- connectedProfile.address,
+ connectedProfile.value.address,
receiver.value?.address,
asset.value.tokenId,
false,
'0x'
)
- .send({ from: connectedProfile.address })
+ .send({ from: connectedProfile.value.address })
assertNotUndefined(asset.value.address, 'asset')
- removeNft(asset.value.address, asset.value.tokenId)
+ assetRepository.removeAsset(asset.value.address, asset.value.tokenId)
break
default:
console.error('Unknown token type')
@@ -155,12 +160,12 @@ const handleSend = async () => {
}
const updateLyxBalance = async () => {
- assertString(connectedProfile.address)
- connectedProfile.balance = await getBalance(connectedProfile.address)
+ assertString(connectedProfile.value?.address)
+ const balance = await getBalance(connectedProfile.value.address)
- if (viewedProfile.address === connectedProfile.address) {
- viewedProfile.balance = connectedProfile.balance
- }
+ useRepo(ProfileModel)
+ .where('address', connectedProfile.value.address)
+ .update({ balance })
}
diff --git a/pages/[profileAddress]/token/[tokenAddress].vue b/pages/[profileAddress]/token/[tokenAddress].vue
deleted file mode 100644
index 1f805e87..00000000
--- a/pages/[profileAddress]/token/[tokenAddress].vue
+++ /dev/null
@@ -1,111 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{
- $formatMessage('token_details_send', {
- token: token?.symbol || '',
- })
- }}
-
-
-
-
{{ token?.name }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pages/asset/[nftAddress]/tokenId/[tokenId].vue b/pages/asset/[nftAddress]/tokenId/[tokenId].vue
new file mode 100644
index 00000000..ea7d07ed
--- /dev/null
+++ b/pages/asset/[nftAddress]/tokenId/[tokenId].vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ asset?.name }}
+
+
+
+
+
+
+
+
+
{{
+ $formatMessage('token_details_send', {
+ token: asset?.symbol || '',
+ })
+ }}
+
+
+
+
+ {{ asset?.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/asset/[tokenAddress].vue b/pages/asset/[tokenAddress].vue
new file mode 100644
index 00000000..0a64aa80
--- /dev/null
+++ b/pages/asset/[tokenAddress].vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{
+ $formatMessage('token_details_send', {
+ token: asset?.symbol || '',
+ })
+ }}
+
+
+
+
{{ asset?.name }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index.vue b/pages/index.vue
index e4149ce1..ffed6bd8 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -3,18 +3,20 @@ definePageMeta({
layout: 'landing',
})
+const { isConnected, isLoadedApp, connectedProfileAddress } = storeToRefs(
+ useAppStore()
+)
+const { isUniversalProfileExtension } = useBrowserExtension()
+
const supportedBrowsers = Object.entries(EXTENSION_STORE_LINKS)
.filter(entry => entry[1] !== '')
.map(browser => browser[0])
-const { status, profile } = useConnectedProfileStore()
-const { isUniversalProfileExtension } = useBrowserExtension()
-
watchEffect(() => {
- if (status.isConnected) {
+ if (isConnected.value) {
try {
- assertAddress(profile.address, 'profile')
- navigateTo(profileRoute(profile.address))
+ assertAddress(connectedProfileAddress.value, 'profile')
+ navigateTo(profileRoute(connectedProfileAddress.value))
} catch (error) {
console.error(error)
}
@@ -26,8 +28,8 @@ watchEffect(() => {
@@ -80,6 +82,6 @@ watchEffect(() => {
-
+
diff --git a/repositories/asset.ts b/repositories/asset.ts
new file mode 100644
index 00000000..7914586d
--- /dev/null
+++ b/repositories/asset.ts
@@ -0,0 +1,190 @@
+import { Repository } from 'pinia-orm'
+
+import { Asset, AssetModel } from '@/models/asset'
+import { InterfaceId } from '@/types/assets'
+
+export class AssetRepository extends Repository