@@ -243,7 +249,6 @@ import {
} from '@/utils/format/balance'
import { getNumberSumOfObjectField } from '@/utils/math'
import { useFiatStore } from '@/stores/fiat'
-import { useIdentityStore } from '@/stores/identity'
import Avatar from '@/components/shared/Avatar.vue'
import Identity from '@/components/identity/IdentityIndex.vue'
import { getMovedItemToFront } from '@/utils/objects'
@@ -260,6 +265,9 @@ import {
} from '@kodadot1/brick'
import TransferTokenTabs, { TransferTokenTab } from './TransferTokenTabs.vue'
import { TokenDetails } from '@/composables/useToken'
+import AddressInput from '@/components/shared/AddressInput.vue'
+import TransactionLoader from '@/components/shared/TransactionLoader.vue'
+import { ApiPromise } from '@polkadot/api'
const Money = defineAsyncComponent(
() => import('@/components/shared/format/Money.vue')
)
@@ -269,34 +277,45 @@ const router = useRouter()
const { $consola, $i18n } = useNuxtApp()
const { unit, decimals } = useChain()
const { apiInstance } = useApi()
-const { urlPrefix, setUrlPrefix } = usePrefix()
+const { urlPrefix } = usePrefix()
const { isLogIn, accountId } = useAuth()
-const identityStore = useIdentityStore()
+const { getBalance } = useBalance()
const { fetchFiatPrice, getCurrentTokenValue } = useFiatStore()
const { initTransactionLoader, isLoading, resolveStatus, status } =
useTransactionStatus()
const { toast } = useToast()
const isTransferModalVisible = ref(false)
+const isLoaderModalVisible = ref(false)
+
+watch(isLoading, (newValue, oldValue) => {
+ // trigger modal only when loading change from false => true
+ // we want to keep modal open when loading changes true => false
+ if (newValue && !oldValue) {
+ isLoaderModalVisible.value = isLoading.value
+ }
+})
export type TargetAddress = {
- address?: string
+ address: string
usd?: number | string
token?: number | string
}
const isMobile = computed(() => useWindowSize().width.value <= 1024)
+const balance = computed(() => getBalance(unit.value) || 0)
const transactionValue = ref('')
const sendSameAmount = ref(false)
const displayUnit = ref<'token' | 'usd'>('token')
const { getTokenIconBySymbol } = useIcon()
-const { tokens, getPrefixByToken, availableTokens } = useToken()
+
+const { tokens } = useToken()
const selectedTabFirst = ref(true)
const tokenIcon = computed(() => getTokenIconBySymbol(unit.value))
const tokenTabs = ref([])
-const targetAddresses = ref([{}])
+const targetAddresses = ref([{ address: '' }])
const hasValidTarget = computed(() =>
targetAddresses.value.some((item) => isAddress(item.address) && item.token)
@@ -308,8 +327,6 @@ const displayTotalValue = computed(() =>
: [`${totalTokenAmount.value} ${unit.value}`, `$${totalUsdValue.value}`]
)
-const balance = computed(() => identityStore.getAuthBalance)
-
const disabled = computed(
() =>
!isLogIn.value ||
@@ -319,20 +336,16 @@ const disabled = computed(
const handleTokenSelect = (newToken: string) => {
selectedTabFirst.value = false
- const token = tokens.value.find((t) => t.symbol === newToken)
- if (token) {
- const chain = getPrefixByToken(token.symbol)
-
- if (!chain) {
- $consola.error(
- `[ERR: INVALID TOKEN] Chain for token ${token.symbol} is not valid`
- )
- return
- }
+ const token = tokens.value.find((t) => t.symbol === newToken)
- setUrlPrefix(chain)
+ if (!token) {
+ return
}
+
+ routerReplace({
+ params: { prefix: token.defaultChain },
+ })
}
const generateTokenTabs = (
@@ -478,11 +491,7 @@ const unifyAddressAmount = (target: TargetAddress) => {
const updateTargetAdressesOnTokenSwitch = () => {
targetAddresses.value.forEach((targetAddress) => {
- if (displayUnit.value === 'usd') {
- onUsdFieldChange(targetAddress)
- } else {
- onAmountFieldChange(targetAddress)
- }
+ onUsdFieldChange(targetAddress)
})
}
@@ -503,39 +512,71 @@ const handleOpenConfirmModal = () => {
}
}
+const getAmountToTransfer = (amount: number, decimals: number) =>
+ String(calculateBalance(Number(amount), decimals))
+
+interface TransferParams {
+ api: ApiPromise
+ decimals: number
+}
+
+const getMultipleAddressesTransferParams = ({
+ api,
+ decimals,
+}: TransferParams) => {
+ const arg = [
+ targetAddresses.value.map((target) => {
+ const amountToTransfer = getAmountToTransfer(
+ target.token as number,
+ decimals
+ )
+
+ return api.tx.balances.transfer(
+ target.address as string,
+ amountToTransfer
+ )
+ }),
+ ]
+
+ return [api.tx.utility.batch, arg]
+}
+
+const getSingleAddressTransferParams = ({ api, decimals }: TransferParams) => {
+ const target = targetAddresses.value[0]
+
+ const amountToTransfer = getAmountToTransfer(target.token as number, decimals)
+
+ return [
+ api.tx.balances.transfer,
+ [target.address as string, amountToTransfer],
+ ]
+}
+
const submit = async (
event: any,
usedNodeUrls: string[] = []
): Promise => {
- showNotification(`${route.query.target ? 'Sent for Sign' : 'Dispatched'}`)
isTransferModalVisible.value = false
initTransactionLoader()
try {
const api = await apiInstance.value
const numOfTargetAddresses = targetAddresses.value.length
- const cb =
- numOfTargetAddresses > 1 ? api.tx.utility.batch : api.tx.balances.transfer
- const arg =
- numOfTargetAddresses > 1
- ? [
- targetAddresses.value.map((target) => {
- const amountToTransfer = String(
- calculateBalance(Number(target.token), decimals.value)
- )
- return api.tx.balances.transfer(
- target.address as string,
- amountToTransfer
- )
- }),
- ]
- : [
- targetAddresses.value[0].address as string,
- calculateBalance(
- Number(targetAddresses.value[0].token),
- decimals.value
- ),
- ]
+ const multipleAddresses = numOfTargetAddresses > 1
+
+ let cb, arg
+
+ if (multipleAddresses) {
+ ;[cb, arg] = getMultipleAddressesTransferParams({
+ api,
+ decimals: decimals.value as number,
+ })
+ } else {
+ ;[cb, arg] = getSingleAddressTransferParams({
+ api,
+ decimals: decimals.value as number,
+ })
+ }
const tx = await exec(
accountId.value,
@@ -543,20 +584,20 @@ const submit = async (
cb,
arg,
txCb(
- async (blockHash) => {
+ () => {
transactionValue.value = execResultValue(tx)
- const header = await api.rpc.chain.getHeader(blockHash)
- const blockNumber = header.number.toString()
- showNotification(
- `[${unit.value}] Transfered ${totalTokenAmount.value} ${unit.value} in block ${blockNumber}`,
- notificationTypes.success
- )
+ targetAddresses.value = [{ address: '' }]
- targetAddresses.value = [{}]
- if (route.query && !route.query.donation) {
- router.push(route.path)
- }
+ // not sure what is the purpose of this
+ // but it causes the explorer url in Transaction Loader to become wrong
+ // after the transaction is finalized
+ // also causes:
+ //https://github.com/kodadot/nft-gallery/issues/6944
+
+ // if (route.query && !route.query.donation) {
+ // router.push(route.path)
+ // }
isLoading.value = false
},
@@ -572,6 +613,7 @@ const submit = async (
if (e.message === 'Cancelled') {
showNotification(e.message, notificationTypes.warn)
isLoading.value = false
+ isLoaderModalVisible.value = false
return
}
@@ -625,48 +667,26 @@ const addAddress = () => {
})
}
-const syncQueryToken = () => {
- const { query } = route
-
- const token = query.token?.toString()
-
- if (!token || !availableTokens.includes(token)) {
- return
- }
-
- const chain = getPrefixByToken(token)
-
- if (!chain) {
- return
- }
-
- setUrlPrefix(chain)
-}
-
-watch(
- route,
- () => {
- syncQueryToken()
- },
- { immediate: true, deep: true }
-)
-
onMounted(() => {
fetchFiatPrice().then(checkQueryParams)
})
+const routerReplace = ({ params = {}, query = {} }) => {
+ router
+ .replace({
+ params: params,
+ query: {
+ ...route.query,
+ ...query,
+ },
+ })
+ .catch(() => null) // null to further not throw navigation errors
+}
+
watchDebounced(
() => targetAddresses.value[0].usd,
(usdamount) => {
- router
- .replace({
- query: {
- ...route.query,
- usdamount: (usdamount || 0).toString(),
- token: unit.value,
- },
- })
- .catch(() => null) // null to further not throw navigation errors
+ routerReplace({ query: { usdamount: (usdamount || 0).toString() } })
},
{ debounce: 300 }
)
diff --git a/composables/massmint/parsers/parseTxt.ts b/composables/massmint/parsers/parseTxt.ts
index a28a8e0a98..88e61a9b99 100644
--- a/composables/massmint/parsers/parseTxt.ts
+++ b/composables/massmint/parsers/parseTxt.ts
@@ -56,26 +56,46 @@ function parsePriceAndCurrency(fieldValue: string): {
return { price: undefined, currency: undefined }
}
+const updateEntry = (entry, line) => {
+ const parsedField = parseField(line)
+
+ if (!parsedField) {
+ return
+ }
+ const { fieldName, fieldValue } = parsedField
+ if (fieldName === 'price') {
+ const { price, currency } = parsePriceAndCurrency(fieldValue)
+ entry.price = price
+ entry.currency = currency
+ } else {
+ entry[fieldName] = fieldValue
+ }
+}
+
function processBlock(block: string): Partial {
const lines = block.split(/\r?\n/)
const entry: Partial = {}
- for (const line of lines) {
- const parsedField = parseField(line)
-
- if (parsedField) {
- const { fieldName, fieldValue } = parsedField
- if (fieldName === 'price') {
- const { price, currency } = parsePriceAndCurrency(fieldValue)
- entry.price = price
- entry.currency = currency
- } else {
- entry[fieldName] = fieldValue
- }
+ lines.forEach((line) => updateEntry(entry, line))
+ return entry
+}
+
+const updateEntries = (entries, block) => {
+ const entry = processBlock(block)
+ const { $consola } = useNuxtApp()
+
+ if (entry.file) {
+ entries[entry.file] = {
+ file: entry.file,
+ name: entry.name,
+ description: entry.description,
+ price: entry.price,
+ currency: entry.currency,
+ valid: isValidEntry(entry),
}
+ } else {
+ $consola.error('Unable to extract file name from invalid block')
}
-
- return entry
}
export function parseTxt(
@@ -87,26 +107,11 @@ export function parseTxt(
const entries: Record = {}
- for (const block of blocks) {
- const entry = processBlock(block)
-
- if (entry.file) {
- entries[entry.file] = {
- file: entry.file,
- name: entry.name || undefined,
- description: entry.description || undefined,
- price: entry.price || undefined,
- currency: entry.currency || undefined,
- valid: isValidEntry(entry),
- }
- } else {
- $consola.error('Unable to extract file name from invalid block')
- }
- }
+ blocks.forEach((block) => updateEntries(entries, block))
if (Object.keys(entries).length === 0) {
$consola.error('Invalid TXT file structure')
- return undefined
+ return
}
return entries
diff --git a/composables/transaction/mintToken/transactionMintBasilisk.ts b/composables/transaction/mintToken/transactionMintBasilisk.ts
index 23d3fb748c..006915f1b1 100644
--- a/composables/transaction/mintToken/transactionMintBasilisk.ts
+++ b/composables/transaction/mintToken/transactionMintBasilisk.ts
@@ -1,12 +1,8 @@
-import type {
- ActionMintToken,
- MintTokenParams,
- MintedCollection,
- TokenToMint,
-} from '../types'
+import type { ActionMintToken, MintedCollection, TokenToMint } from '../types'
import { isRoyaltyValid } from '@/utils/royalty'
import { constructMeta } from './constructMeta'
import { BaseMintedCollection } from '@/components/base/types'
+import { expandCopies, transactionFactory } from './utils'
const prepareTokenMintArgs = async (
token: TokenToMint,
@@ -42,8 +38,9 @@ const prepareTokenMintArgs = async (
const getArgs = async (item: ActionMintToken, api) => {
const { $consola } = useNuxtApp()
- const tokens = Array.isArray(item.token) ? item.token : [item.token]
-
+ const tokens = expandCopies(
+ Array.isArray(item.token) ? item.token : [item.token]
+ )
const arg = (
await Promise.all(
tokens.map((token, i) => {
@@ -60,34 +57,4 @@ const getArgs = async (item: ActionMintToken, api) => {
return [arg]
}
-export async function execMintBasilisk({
- item,
- api,
- executeTransaction,
- isLoading,
- status,
-}: MintTokenParams) {
- const { $i18n } = useNuxtApp()
- isLoading.value = true
- status.value = 'loader.ipfs'
- const args = await getArgs(item, api)
-
- const nameInNotifications = Array.isArray(item.token)
- ? item.token.map((t) => t.name).join(', ')
- : item.token.name
-
- executeTransaction({
- cb: api.tx.utility.batchAll,
- arg: args,
- successMessage:
- item.successMessage ||
- ((blockNumber) =>
- $i18n.t('mint.mintNFTSuccess', {
- name: nameInNotifications,
- block: blockNumber,
- })),
- errorMessage:
- item.errorMessage ||
- $i18n.t('mint.errorCreateNewNft', { name: nameInNotifications }),
- })
-}
+export const execMintBasilisk = transactionFactory(getArgs)
diff --git a/composables/transaction/mintToken/transactionMintRmrk.ts b/composables/transaction/mintToken/transactionMintRmrk.ts
index e097193f94..219b9799e5 100644
--- a/composables/transaction/mintToken/transactionMintRmrk.ts
+++ b/composables/transaction/mintToken/transactionMintRmrk.ts
@@ -26,7 +26,7 @@ import {
} from '../types'
import { constructMeta } from './constructMeta'
import { isRoyaltyValid } from '@/utils/royalty'
-import { calculateFees, copiesToMint } from './utils'
+import { calculateFees, copiesToMint, getNameInNotifications } from './utils'
const getOnChainProperties = ({ tags, royalty, hasRoyalty }: TokenToMint) => {
let onChainProperties = convertAttributesToProperties(tags)
@@ -151,9 +151,7 @@ export async function execMintRmrk({
status.value = 'loader.ipfs'
const { args, createdNFTs } = await getArgs(item, api)
- const nameInNotifications = Array.isArray(item.token)
- ? item.token.map((t) => t.name).join(', ')
- : item.token.name
+ const nameInNotifications = getNameInNotifications(item)
const isSingle = args.length === 1
const cb = isSingle ? api.tx.system.remark : api.tx.utility.batchAll
diff --git a/composables/transaction/mintToken/transactionMintStatemine.ts b/composables/transaction/mintToken/transactionMintStatemine.ts
index 4bf171cb17..5dec084a50 100644
--- a/composables/transaction/mintToken/transactionMintStatemine.ts
+++ b/composables/transaction/mintToken/transactionMintStatemine.ts
@@ -1,12 +1,8 @@
import { BaseMintedCollection } from '@/components/base/types'
-import type {
- ActionMintToken,
- MintTokenParams,
- MintedCollection,
-} from '../types'
+import type { ActionMintToken, MintedCollection } from '../types'
import { TokenToMint } from '../types'
import { constructMeta } from './constructMeta'
-import { calculateFees, copiesToMint } from './utils'
+import { calculateFees, expandCopies, transactionFactory } from './utils'
import { canSupport } from '@/utils/support'
type id = { id: number }
@@ -27,24 +23,6 @@ export const assignIds = (tokens: T[]): (T & id)[] => {
})
}
-export const expandCopies = (tokens: T[]): T[] => {
- return tokens.flatMap((token) => {
- const copies = copiesToMint(token)
- if (copies === 1) {
- return token
- }
-
- return Array(copies)
- .fill(null)
- .map((_, index) => {
- return {
- ...token,
- name: token.postfix ? `${token.name} #${index + 1}` : token.name,
- }
- })
- })
-}
-
export const prepareTokenMintArgs = async (token: TokenToMint & id, api) => {
const { id: collectionId } = token.selectedCollection as BaseMintedCollection
const { price, id: nextId } = token
@@ -117,35 +95,4 @@ const getArgs = async (item: ActionMintToken, api) => {
return [[...arg.flat(), ...supportInteraction]]
}
-export async function execMintStatemine({
- item,
- api,
- executeTransaction,
- isLoading,
- status,
-}: MintTokenParams) {
- const { $i18n } = useNuxtApp()
-
- isLoading.value = true
- status.value = 'loader.ipfs'
- const args = await getArgs(item, api)
-
- const nameInNotifications = Array.isArray(item.token)
- ? item.token.map((t) => t.name).join(', ')
- : item.token.name
-
- executeTransaction({
- cb: api.tx.utility.batchAll,
- arg: args,
- successMessage:
- item.successMessage ||
- ((blockNumber) =>
- $i18n.t('mint.mintNFTSuccess', {
- name: nameInNotifications,
- block: blockNumber,
- })),
- errorMessage:
- item.errorMessage ||
- $i18n.t('mint.errorCreateNewNft', { name: nameInNotifications }),
- })
-}
+export const execMintStatemine = transactionFactory(getArgs)
diff --git a/composables/transaction/mintToken/utils.ts b/composables/transaction/mintToken/utils.ts
index c45dc0ef05..b2d613d1b7 100644
--- a/composables/transaction/mintToken/utils.ts
+++ b/composables/transaction/mintToken/utils.ts
@@ -1,5 +1,11 @@
import { usePreferencesStore } from '@/stores/preferences'
-import { Max, MintedCollection, TokenToMint } from '../types'
+import {
+ ActionMintToken,
+ Max,
+ MintTokenParams,
+ MintedCollection,
+ TokenToMint,
+} from '../types'
export const copiesToMint = (token: T): number => {
const { copies, selectedCollection } = token
@@ -22,3 +28,55 @@ export const calculateFees = () => {
return { enabledFees, feeMultiplier }
}
+
+export const getNameInNotifications = (item: ActionMintToken) => {
+ return Array.isArray(item.token)
+ ? item.token.map((t) => t.name).join(', ')
+ : item.token.name
+}
+
+export const transactionFactory = (getArgs) => {
+ return async (mintTokenParams: MintTokenParams) => {
+ const { item, api, executeTransaction, isLoading, status } = mintTokenParams
+ const { $i18n } = useNuxtApp()
+
+ isLoading.value = true
+ status.value = 'loader.ipfs'
+ const args = await getArgs(item, api)
+
+ const nameInNotifications = getNameInNotifications(item)
+
+ executeTransaction({
+ cb: api.tx.utility.batchAll,
+ arg: args,
+ successMessage:
+ item.successMessage ||
+ ((blockNumber) =>
+ $i18n.t('mint.mintNFTSuccess', {
+ name: nameInNotifications,
+ block: blockNumber,
+ })),
+ errorMessage:
+ item.errorMessage ||
+ $i18n.t('mint.errorCreateNewNft', { name: nameInNotifications }),
+ })
+ }
+}
+
+export const expandCopies = (tokens: T[]): T[] => {
+ return tokens.flatMap((token) => {
+ const copies = copiesToMint(token)
+ if (copies === 1) {
+ return token
+ }
+
+ return Array(copies)
+ .fill(null)
+ .map((_, index) => {
+ return {
+ ...token,
+ name: token.postfix ? `${token.name} #${index + 1}` : token.name,
+ }
+ })
+ })
+}
diff --git a/composables/transaction/transactionList.ts b/composables/transaction/transactionList.ts
index 3b867c54ac..3d2d2adf4f 100644
--- a/composables/transaction/transactionList.ts
+++ b/composables/transaction/transactionList.ts
@@ -51,78 +51,84 @@ const createKusamaInteraction = (item: ActionList) => {
.filter((interaction): interaction is string => interaction !== undefined)
}
-export function execListTx(item: ActionList, api, executeTransaction) {
- const isSingle = !Array.isArray(item.token)
+const execKsm = (isSingle: boolean, item: ActionList, api) => {
+ const interaction = createKusamaInteraction(item)
+ if (!interaction) {
+ return
+ }
+ const args = isSingle
+ ? interaction
+ : (interaction as string[]).map((interaction) =>
+ api.tx.system.remark(interaction)
+ )
+ const cb = isSingle ? api.tx.system.remark : api.tx.utility.batchAll
+ return {
+ cb,
+ arg: [args],
+ }
+}
- if (item.urlPrefix === 'rmrk' || item.urlPrefix === 'ksm') {
- const interaction = createKusamaInteraction(item)
- if (!interaction) {
- return
+const execBsx = (isSingle: boolean, item: ActionList, api) => {
+ if (isSingle) {
+ const token = item.token as TokenToList
+ return {
+ cb: getApiCall(api, item.urlPrefix, Interaction.LIST),
+ arg: bsxParamResolver(token.nftId, Interaction.LIST, token.price),
}
- const args = isSingle
- ? interaction
- : (interaction as string[]).map((interaction) =>
- api.tx.system.remark(interaction)
- )
- const cb = isSingle ? api.tx.system.remark : api.tx.utility.batchAll
- executeTransaction({
- cb,
+ } else {
+ const tokens = item.token as TokenToList[]
+ const cb = getApiCall(api, item.urlPrefix, Interaction.LIST)
+ const args = tokens.map((token) =>
+ cb(...bsxParamResolver(token.nftId, Interaction.LIST, token.price))
+ )
+ return {
+ cb: api.tx.utility.batchAll,
arg: [args],
- successMessage: item.successMessage,
- errorMessage: item.errorMessage,
- })
+ }
+ }
+}
+const execAhkOrAhp = (isSingle: boolean, item: ActionList, api) => {
+ const getParams = (token: TokenToList) => {
+ const legacy = isLegacy(token.nftId)
+ const paramResolver = assetHubParamResolver(legacy)
+ return { legacy, paramResolver }
}
- if (item.urlPrefix === 'snek' || item.urlPrefix === 'bsx') {
- if (isSingle) {
- const token = item.token as TokenToList
- executeTransaction({
- cb: getApiCall(api, item.urlPrefix, Interaction.LIST),
- arg: bsxParamResolver(token.nftId, Interaction.LIST, token.price),
- successMessage: item.successMessage,
- errorMessage: item.errorMessage,
- })
- } else {
- const tokens = item.token as TokenToList[]
- const cb = getApiCall(api, item.urlPrefix, Interaction.LIST)
- const args = tokens.map((token) =>
- cb(...bsxParamResolver(token.nftId, Interaction.LIST, token.price))
- )
- executeTransaction({
- cb: api.tx.utility.batchAll,
- arg: [args],
- successMessage: item.successMessage,
- errorMessage: item.errorMessage,
- })
+ if (isSingle) {
+ const token = item.token as TokenToList
+ const { legacy, paramResolver } = getParams(token)
+ return {
+ cb: getApiCall(api, item.urlPrefix, Interaction.LIST, legacy),
+ arg: paramResolver(token.nftId, Interaction.LIST, token.price),
}
+ } else {
+ const tokens = item.token as TokenToList[]
+ const args = tokens.map((token) => {
+ const { legacy, paramResolver } = getParams(token)
+ const cb = getApiCall(api, item.urlPrefix, Interaction.LIST, legacy)
+ return cb(...paramResolver(token.nftId, Interaction.LIST, token.price))
+ })
+
+ return { cb: api.tx.utility.batchAll, arg: [args] }
}
+}
- if (item.urlPrefix === 'ahk' || item.urlPrefix === 'ahp') {
- if (isSingle) {
- const token = item.token as TokenToList
- const legacy = isLegacy(token.nftId)
- const paramResolver = assetHubParamResolver(legacy)
- executeTransaction({
- cb: getApiCall(api, item.urlPrefix, Interaction.LIST, legacy),
- arg: paramResolver(token.nftId, Interaction.LIST, token.price),
- successMessage: item.successMessage,
- errorMessage: item.errorMessage,
- })
- } else {
- const tokens = item.token as TokenToList[]
- const args = tokens.map((token) => {
- const legacy = isLegacy(token.nftId)
- const cb = getApiCall(api, item.urlPrefix, Interaction.LIST, legacy)
- const paramResolver = assetHubParamResolver(legacy)
- return cb(...paramResolver(token.nftId, Interaction.LIST, token.price))
- })
-
- executeTransaction({
- cb: api.tx.utility.batchAll,
- arg: [args],
- successMessage: item.successMessage,
- errorMessage: item.errorMessage,
- })
- }
+export function execListTx(item: ActionList, api, executeTransaction) {
+ const isSingle = !Array.isArray(item.token)
+
+ const fnMap = {
+ rmrk: execKsm,
+ ksm: execKsm,
+ snek: execBsx,
+ bsx: execBsx,
+ ahk: execAhkOrAhp,
+ ahp: execAhkOrAhp,
+ }
+
+ const { cb, arg } =
+ fnMap[item.urlPrefix]?.call(null, isSingle, item, api) || {}
+ if (cb && arg) {
+ const { successMessage, errorMessage } = item
+ executeTransaction({ cb, arg, successMessage, errorMessage })
}
}
diff --git a/composables/transaction/transactionOfferAccept.ts b/composables/transaction/transactionOfferAccept.ts
index b1a3ee44fe..8126078ee8 100644
--- a/composables/transaction/transactionOfferAccept.ts
+++ b/composables/transaction/transactionOfferAccept.ts
@@ -1,23 +1,3 @@
-import { warningMessage } from '@/utils/notification'
-import { tokenIdToRoute } from '@/components/unique/utils'
+import { transactionOfferFactory } from './utils'
-import type { ActionWithdrawOffer } from './types'
-
-export function execAcceptOfferTx(
- params: ActionWithdrawOffer,
- api,
- executeTransaction
-) {
- try {
- const { id, item } = tokenIdToRoute(params.nftId)
- const args = [id, item, params.maker]
- const cb = api.tx.marketplace.acceptOffer
-
- executeTransaction({
- cb,
- arg: args,
- })
- } catch (error) {
- warningMessage(error)
- }
-}
+export const execAcceptOfferTx = transactionOfferFactory('acceptOffer')
diff --git a/composables/transaction/transactionOfferWithdraw.ts b/composables/transaction/transactionOfferWithdraw.ts
index 8bb11efc45..761c594306 100644
--- a/composables/transaction/transactionOfferWithdraw.ts
+++ b/composables/transaction/transactionOfferWithdraw.ts
@@ -1,23 +1,3 @@
-import { warningMessage } from '@/utils/notification'
-import { tokenIdToRoute } from '@/components/unique/utils'
+import { transactionOfferFactory } from './utils'
-import type { ActionWithdrawOffer } from './types'
-
-export function execWithdrawOfferTx(
- params: ActionWithdrawOffer,
- api,
- executeTransaction
-) {
- try {
- const { id, item } = tokenIdToRoute(params.nftId)
- const args = [id, item, params.maker]
- const cb = api.tx.marketplace.withdrawOffer
-
- executeTransaction({
- cb,
- arg: args,
- })
- } catch (error) {
- warningMessage(error)
- }
-}
+export const execWithdrawOfferTx = transactionOfferFactory('withdrawOffer')
diff --git a/composables/transaction/utils.ts b/composables/transaction/utils.ts
new file mode 100644
index 0000000000..168512c3a0
--- /dev/null
+++ b/composables/transaction/utils.ts
@@ -0,0 +1,20 @@
+import { warningMessage } from '@/utils/notification'
+import { tokenIdToRoute } from '@/components/unique/utils'
+
+import type { ActionWithdrawOffer } from './types'
+
+export function transactionOfferFactory(key: 'acceptOffer' | 'withdrawOffer') {
+ return function (params: ActionWithdrawOffer, api, executeTransaction) {
+ try {
+ const { id, item } = tokenIdToRoute(params.nftId)
+ const args = [id, item, params.maker]
+ const cb = api.tx.marketplace[key]
+ executeTransaction({
+ cb,
+ arg: args,
+ })
+ } catch (error) {
+ warningMessage(error)
+ }
+ }
+}
diff --git a/composables/useBalance.ts b/composables/useBalance.ts
index 9c2672369d..81a182f43b 100644
--- a/composables/useBalance.ts
+++ b/composables/useBalance.ts
@@ -4,26 +4,33 @@ import { getKusamaAssetId } from '@/utils/api/bsx/query'
export default function () {
const { urlPrefix } = usePrefix()
const identityStore = useIdentityStore()
- const balance = computed(() => {
+
+ const getBalance = (token: string) => {
+ token = token.toLocaleLowerCase()
switch (urlPrefix.value) {
case 'rmrk':
case 'ksm':
case 'ahk':
case 'ahp':
+ case 'dot':
return identityStore.getAuthBalance
case 'bsx':
- return identityStore.multiBalances.chains.basilisk?.ksm?.nativeBalance
+ return identityStore.multiBalances.chains.basilisk?.[token]
+ ?.nativeBalance
case 'snek':
- return identityStore.multiBalances.chains['basilisk-testnet']?.ksm
+ return identityStore.multiBalances.chains['basilisk-testnet']?.[token]
?.nativeBalance
default:
return identityStore.getTokenBalanceOf(
getKusamaAssetId(urlPrefix.value)
)
}
- })
+ }
+
+ const balance = computed(() => getBalance('KSM'))
return {
balance,
+ getBalance,
}
}
diff --git a/composables/useBlockTime.ts b/composables/useBlockTime.ts
new file mode 100644
index 0000000000..c922ccf4a5
--- /dev/null
+++ b/composables/useBlockTime.ts
@@ -0,0 +1,21 @@
+import { PartialConfig } from '@/utils/config/types'
+
+export default function () {
+ const { urlPrefix } = usePrefix()
+
+ const paraChainBlockTime = 12 //seconds
+ const relayChainBlockTime = 6 //seconds
+
+ const chainBlockTimes: PartialConfig = {
+ ksm: relayChainBlockTime,
+ dot: relayChainBlockTime,
+ rmrk: relayChainBlockTime,
+ }
+
+ const blocktime = computed(
+ () => chainBlockTimes[urlPrefix.value] ?? paraChainBlockTime
+ )
+ return {
+ blocktime,
+ }
+}
diff --git a/composables/useNft.ts b/composables/useNft.ts
index 2660b7c4e9..1d737ca966 100644
--- a/composables/useNft.ts
+++ b/composables/useNft.ts
@@ -1,5 +1,6 @@
import type { NFT, NFTMetadata } from '@/components/rmrk/service/scheme'
import { sanitizeIpfsUrl } from '@/utils/ipfs'
+import type { BaseNFTMeta } from '@/components/base/types'
import { processSingleMetadata } from '@/utils/cachingStrategy'
import { getMimeType } from '@/utils/gallery/media'
import unionBy from 'lodash/unionBy'
@@ -22,7 +23,7 @@ export type ItemResources = {
}
export type NFTWithMetadata = NFT &
- NFTMetadata & { meta: NFTMetadata } & ItemResources
+ NFTMetadata & { meta: BaseNFTMeta } & ItemResources
function getGeneralMetadata(nft: NFTWithMetadata) {
return {
@@ -30,7 +31,7 @@ function getGeneralMetadata(nft: NFTWithMetadata) {
name: nft.name || nft.meta.name || nft.id,
description: nft.description || nft.meta.description || '',
image: sanitizeIpfsUrl(nft.meta.image),
- animation_url: sanitizeIpfsUrl(nft.meta.animation_url || ''),
+ animationUrl: sanitizeIpfsUrl(nft.meta.animation_url || ''),
type: nft.meta.type || '',
}
}
@@ -53,7 +54,7 @@ async function getProcessMetadata(nft: NFTWithMetadata) {
nft.metadata
)) as NFTWithMetadata
const image = sanitizeIpfsUrl(metadata.image || metadata.mediaUri || '')
- const animation_url = sanitizeIpfsUrl(metadata.animation_url || '')
+ const animationUrl = sanitizeIpfsUrl(metadata.animation_url || '')
const getAttributes = () => {
const hasMetadataAttributes =
metadata.attributes && metadata.attributes.length > 0
@@ -73,7 +74,7 @@ async function getProcessMetadata(nft: NFTWithMetadata) {
name: nft.name || metadata.name || nft.id,
description: nft.description || metadata.description || '',
image,
- animation_url,
+ animationUrl,
type: metadata.type || '',
attributes: getAttributes(),
}
diff --git a/composables/useToken.ts b/composables/useToken.ts
index 11ddb07958..1cdb554a8f 100644
--- a/composables/useToken.ts
+++ b/composables/useToken.ts
@@ -1,74 +1,40 @@
import { useFiatStore } from '@/stores/fiat'
-import { type ChainProperties, type Prefix } from '@kodadot1/static'
-import { chainPropListOf } from '@/utils/config/chain.config'
-import { groupByNestedProperty } from '@/utils/objects'
-import { availablePrefixes } from '@/utils/chain'
+import { type Prefix } from '@kodadot1/static'
+import { useIdentityStore } from '@/stores/identity'
+import { defultTokenChain } from '@/utils/config/chain.config'
export interface TokenDetails {
symbol: string
value: number | string | null
icon: string
- chains: Prefix[]
+ defaultChain: Prefix
}
+const getAssetToken = (asset) => asset?.token || 'KSM'
+const getUniqueArrayItems = (items: string[]) => [...new Set(items)]
+
export default function useToken() {
const { getCurrentTokenValue } = useFiatStore()
const { getTokenIconBySymbol } = useIcon()
+ const { multiBalanceAssets } = useIdentityStore()
- const availableChains = availablePrefixes().map(
- (item) => item.value as Prefix
- )
-
- const availableTokens = ['BSX', 'DOT', 'KSM']
-
- const chainsProperties = computed(() => {
- return availableChains.reduce(
- (reducer, chain: Prefix) => ({
- ...reducer,
- [chain]: chainPropListOf(chain),
- }),
- {}
- ) as { [k in Prefix]: ChainProperties }[]
- })
-
- const groupedTokensByChains = computed(() =>
- groupByNestedProperty(chainsProperties.value, 'tokenSymbol')
+ const availableAssets = computed(() => multiBalanceAssets)
+ const availableTokensAcrossAllChains = computed(() =>
+ getUniqueArrayItems(Object.values(availableAssets.value).map(getAssetToken))
)
- const getTokenChain = (token: string): Prefix[] =>
- groupedTokensByChains.value[token] || []
-
const tokens = computed(() => {
- const filteredTokens = Object.keys(groupedTokensByChains.value).filter(
- (token) => availableTokens.includes(token)
- )
-
- return filteredTokens.map((tokenSymbol) => {
+ return availableTokensAcrossAllChains.value.map((tokenSymbol) => {
return {
symbol: tokenSymbol as string,
value: getCurrentTokenValue(tokenSymbol),
icon: getTokenIconBySymbol(tokenSymbol),
- chains: getTokenChain(tokenSymbol),
+ defaultChain: defultTokenChain[tokenSymbol],
}
})
})
- const getPrefixByToken = (token: string): Prefix | null => {
- switch (token.toLowerCase()) {
- case 'bsx':
- return 'bsx'
- case 'dot':
- return 'dot'
- case 'ksm':
- return 'ksm'
- default:
- return null
- }
- }
-
return {
tokens,
- availableTokens,
- getPrefixByToken,
}
}
diff --git a/composables/useTransactionStatus.ts b/composables/useTransactionStatus.ts
index d11216c946..4235583685 100644
--- a/composables/useTransactionStatus.ts
+++ b/composables/useTransactionStatus.ts
@@ -1,39 +1,55 @@
import { ExtrinsicStatus } from '@polkadot/types/interfaces'
+export enum TransactionStatus {
+ Broadcast = 'loader.broadcast',
+ Casting = 'loader.casting',
+ Sign = 'loader.sign',
+ Block = 'loader.block',
+ Finalized = 'loader.finalized',
+ Unknown = '',
+ IPFS = 'loader.ipfs',
+}
+
function useTransactionStatus() {
- const status = ref('')
+ const status = ref(TransactionStatus.Unknown)
const isLoading = ref(false)
const resolveStatus = (
extrinsicStatus: ExtrinsicStatus,
omitFinalized?: boolean
): void => {
+ if (extrinsicStatus.isBroadcast) {
+ status.value = TransactionStatus.Broadcast
+ return
+ }
if (extrinsicStatus.isReady) {
- status.value = 'loader.casting'
+ status.value = TransactionStatus.Casting
return
}
if (extrinsicStatus.isInBlock) {
- status.value = 'loader.block'
+ status.value = TransactionStatus.Block
return
}
if (extrinsicStatus.isFinalized) {
- status.value = omitFinalized ? '' : 'loader.finalized'
+ status.value = omitFinalized
+ ? TransactionStatus.Unknown
+ : TransactionStatus.Finalized
return
}
- status.value = ''
+ status.value = TransactionStatus.Unknown
}
const initTransactionLoader = (): void => {
isLoading.value = true
- status.value = 'loader.sign'
+ status.value = TransactionStatus.Unknown
}
const stopLoader = (): void => {
isLoading.value = false
- status.value = ''
+ status.value = TransactionStatus.Unknown
}
return {
status,
diff --git a/content/blog/first-time.md b/content/blog/first-time.md
index e555b293a8..59c1682421 100644
--- a/content/blog/first-time.md
+++ b/content/blog/first-time.md
@@ -69,6 +69,38 @@ docker-compose up
Voila! KodaDot will be available at [localhost:9090](http://localhost:9090).
KodaDot supports Hot Module Replacement on docker; any changes made will take effect immediately.
+## Submitting a pull request
+
+To enhance your workflow with our repository, we suggest making use of the [GitHub CLI](https://cli.github.com/) to initiate a pull request. This is particularly beneficial since we offer a selection of predefined templates for your convenience.
+
+To get started ensure that you are authenticated on the GitHub CLI and the remote repository has been set. You can find a quickstart guide [here](https://docs.github.com/en/github-cli/github-cli/quickstart).
+
+1. To submit a new pull request use the command in your terminal:
+
+```bash
+gh pr create
+```
+
+2. You'll be prompted to select a branch. You can select your working branch or skip the step if you have already pushed your branch.
+
+3. Next you'll be prompted for a title and to choose a template. Please use one of the predefined templates that is relevant to your pull request. You will also be given an option to edit it as required.
+
+```bash
+Creating pull request for username:example-branch into main in kodadot/nft-gallery
+
+? Title example-branch-title
+? Choose a template [Use arrows to move, type to filter]
+> PULL_REQUEST_TEMPLATE.md
+ QUICK_AND_DESIGN_PULL_REQUEST_TEMPLATE.md
+ QUICK_AND_QA_PULL_REQUEST_TEMPLATE.md
+ QUICK_PULL_REQUEST_TEMPLATE.md
+ Open a blank pull request
+```
+
+4. Once you have edited the template, submit your pull request. 🚀
+
+Alternatively, if you cannot use GitHub CLI, you can find the templates [here](https://github.com/kodadot/nft-gallery/tree/main/.github/PULL_REQUEST_TEMPLATE) and create a pull request on github.
+
## Dev hacks (FAQ) 🦇
**0. How can I resolve conflict on pnpm-lock.yaml?**
@@ -188,7 +220,6 @@ Current Indexers we have/use:
- SubSquid
- Basilisk: [snek](https://github.com/kodadot/snek)
- Kusama: [rubick](https://github.com/kodadot/rubick)
- - Basilisk: [snek](https://github.com/kodadot/snek)
- AssetHub: [stick](https://github.com/kodadot/stick)
- MoonRiver: [click](https://github.com/kodadot/click)
diff --git a/libs/ui/src/components/NeoModal/NeoModal.vue b/libs/ui/src/components/NeoModal/NeoModal.vue
index c64677d5c8..9ecbd0d4a4 100644
--- a/libs/ui/src/components/NeoModal/NeoModal.vue
+++ b/libs/ui/src/components/NeoModal/NeoModal.vue
@@ -7,6 +7,7 @@
:can-cancel="canCancel"
:full-screen="fullScreen"
:content-class="[...contentClassName, noShadow ? 'no-shadow' : '']"
+ :mobile-breakpoint="mobileBreakpoint"
:root-class="rootClass"
:style="{
'--max-height': maxHeight,
@@ -29,6 +30,7 @@ const props = withDefaults(
rootClass?: string
noShadow?: boolean
maxHeight?: string | number
+ mobileBreakpoint?: string
}>(),
{
destroyOnHide: true,
@@ -38,6 +40,7 @@ const props = withDefaults(
rootClass: '',
noShadow: false,
maxHeight: '80vh',
+ mobileBreakpoint: '768px',
}
)
diff --git a/libs/ui/src/components/NeoSteps/NeoSteps.scss b/libs/ui/src/components/NeoSteps/NeoSteps.scss
index a1355e7915..01c384e585 100644
--- a/libs/ui/src/components/NeoSteps/NeoSteps.scss
+++ b/libs/ui/src/components/NeoSteps/NeoSteps.scss
@@ -5,14 +5,14 @@
@import '@oruga-ui/oruga/src/scss/utilities/helpers.scss';
$steps-details-background-color: hsla(0, 0%, 0%, 0);
-$steps-details-padding: .5em !default;
-$steps-font-size: .85rem;
-$steps-active-color: #6188E7;
-$steps-previous-color: #6188E7;
+$steps-details-padding: 0.5em !default;
+$steps-font-size: var(--step-size, 0.85rem);
+$steps-active-color: #6188e7;
+$steps-previous-color: #6188e7;
$steps-divider-height: 2px;
$steps-marker-background: #fff;
-$steps-marker-border: .2em solid $grey-light !default;
+$steps-marker-border: 0.2em solid $grey-light !default;
$steps-marker-color: $primary-invert !default;
$steps-marker-font-weight: 700 !default;
@@ -24,3 +24,81 @@ $steps-link-color: hsl(0, 0%, 29%) !default;
$steps-content-padding: 1rem !default;
@import '@oruga-ui/oruga/src/scss/components/steps';
+
+.o-steps {
+ // Base styles - Themed
+ &__marker {
+ @include ktheme() {
+ background: theme('background-color');
+ }
+ }
+
+ .o-icon {
+ @include ktheme() {
+ color: theme('background-color');
+ }
+ }
+
+ &__divider {
+ @include ktheme() {
+ background: linear-gradient(
+ to left,
+ theme('k-shade') 50%,
+ $steps-active-color 50%
+ );
+ background-size: 200% 100%;
+ background-position: right bottom;
+ }
+ }
+
+ // oruga mixes the step size and the titlle font size together ($steps-font-size)
+ // we need to override the title font size to make it bigger (12px / 0.75rem
+ &__title {
+ font-size: 0.75rem !important;
+
+ @include ktheme() {
+ color: theme('k-grey');
+ }
+ }
+
+ // Navigation Items
+ &__nav-item {
+ // force active background color on last nav-item when active
+ &--last {
+ &.o-steps__nav-item-active {
+ .o-steps__marker {
+ background-color: $steps-active-color;
+ }
+ }
+ }
+
+ // Current nav-item styles
+ &-active {
+ .o-steps__title {
+ color: $steps-previous-color;
+ }
+ // move divider to right side (color it active)
+ // this is required becuase of the theme override above of the divider background
+ .o-steps__divider {
+ background-position: left bottom;
+ }
+ }
+
+ // Previous nav-item styles
+ &-previous {
+ .o-steps__marker {
+ border-color: $steps-previous-color;
+ background-color: $steps-previous-color;
+ }
+ .o-steps__title {
+ color: $steps-previous-color;
+ }
+
+ // move divider to right side (color it active)
+ // this is required becuase of the theme override above of the divider background
+ .o-steps__divider {
+ background-position: left bottom;
+ }
+ }
+ }
+}
diff --git a/libs/ui/src/components/NeoSteps/NeoSteps.vue b/libs/ui/src/components/NeoSteps/NeoSteps.vue
index 23f58d28a8..c55d232ae1 100644
--- a/libs/ui/src/components/NeoSteps/NeoSteps.vue
+++ b/libs/ui/src/components/NeoSteps/NeoSteps.vue
@@ -1,9 +1,43 @@
-