diff --git a/constant/index.ts b/constant/index.ts index 96345be34..5d054feb3 100644 --- a/constant/index.ts +++ b/constant/index.ts @@ -15,6 +15,10 @@ export const LIKER_NFT_FEE_WALLET = IS_TESTNET ? 'like1yney2cqn5qdrlc50yr5l53898ufdhxafqz9gxp' : 'like10ywsmztkxjl55xarxnhlxwc83z9v2hkxtsajwl' +export const LIKER_NFT_TARGET_ADDRESS = IS_TESTNET + ? 'like1yney2cqn5qdrlc50yr5l53898ufdhxafqz9gxp' + : 'like10ywsmztkxjl55xarxnhlxwc83z9v2hkxtsajwl' + export const LIKE_CO_API = `https://api.${IS_TESTNET ? 'rinkeby.' : ''}like.co` export const APP_LIKE_CO_URL = `https://app.${IS_TESTNET ? 'rinkeby.' : ''}like.co` export const LIKER_LAND_URL = `https://${LIKER_LAND_HOST}` diff --git a/pages/nft-book-store/new.vue b/pages/nft-book-store/new.vue index 250bf4785..fd5008de0 100644 --- a/pages/nft-book-store/new.vue +++ b/pages/nft-book-store/new.vue @@ -78,7 +78,20 @@ - + + + + + + + ([{ price: MINIMAL_PRICE, + deliverMethod: 'auto', + autoMemo: '', stock: Number(route.query.count as string || 1), nameEn: 'Standard Edition', nameZh: '標準版', @@ -455,7 +471,7 @@ const toolbarOptions = ref([ 'preview' ]) -const isEditMode = computed(() => route.params.editingClassId && editionIndex.value) +const isEditMode = computed(() => Boolean(route.params.editingClassId && editionIndex.value)) const pageTitle = computed(() => isEditMode.value ? 'Edit Current Edition' : 'New NFT Book Listing') const submitButtonText = computed(() => isEditMode.value ? 'Save Changes' : 'Submit') const editionInfo = ref({}) @@ -590,6 +606,8 @@ function addMorePrice () { prices.value.push({ index: uuidv4(), price: MINIMAL_PRICE, + deliverMethod: 'auto', + autoMemo: '', stock: 1, nameEn: `Tier ${nextPriceIndex.value}`, nameZh: `級別 ${nextPriceIndex.value}`, @@ -655,6 +673,8 @@ function mapPrices (prices:any) { priceInDecimal: Math.round(Number(p.price) * 100), price: Number(p.price), stock: Number(p.stock), + isAutoDeliver: p.deliverMethod === 'auto', + autoMemo: p.deliverMethod === 'auto' ? (p.autoMemo || '') : '', hasShipping: p.hasShipping || false })) } @@ -712,6 +732,26 @@ async function submitNewClass () { })) : undefined + const autoDeliverCount = p + .filter(price => price.isAutoDeliver) + .reduce((acc, price) => acc + price.stock, 0) + + let autoDeliverNFTsTxHash = '' + if (autoDeliverCount > 0) { + if (!wallet.value || !signer.value) { + await connect() + } + if (!wallet.value || !signer.value) { + throw new Error('Unable to connect to wallet') + } + autoDeliverNFTsTxHash = await sendNFTsToAPIWallet( + classIdInput.value as string, + autoDeliverCount, + signer.value, + wallet.value + ) + } + await newBookListing(classIdInput.value as string, { defaultPaymentCurrency, connectedWallets, @@ -720,7 +760,8 @@ async function submitNewClass () { prices: p, shippingRates: s, mustClaimToView, - hideDownload + hideDownload, + autoDeliverNFTsTxHash }) router.push({ name: 'nft-book-store' }) } catch (err) { diff --git a/pages/nft-book-store/send/[classId].vue b/pages/nft-book-store/send/[classId].vue index ad5a3e0f5..00fb81706 100644 --- a/pages/nft-book-store/send/[classId].vue +++ b/pages/nft-book-store/send/[classId].vue @@ -146,7 +146,7 @@ import { useBookStoreApiStore } from '~/stores/book-store-api' import { useWalletStore } from '~/stores/wallet' import { useNftStore } from '~/stores/nft' import { parseImageURLFromMetadata } from '~/utils' -import { signExecNFTSendAuthz, signSendNFT } from '~/utils/cosmos' +import { signExecNFTSendAuthz, signSendNFTs } from '~/utils/cosmos' const store = useWalletStore() const { wallet, signer } = storeToRefs(store) @@ -304,10 +304,10 @@ async function onSendNFTStart () { let res: DeliverTxResponse | undefined if (userIsOwner.value) { - res = await signSendNFT( + res = await signSendNFTs( orderInfo.value.wallet, - classId.value, - targetNftId, + [classId.value], + [targetNftId], signer.value, wallet.value, memo.value diff --git a/pages/nft-book-store/status/[classId]/edit/[editionIndex].vue b/pages/nft-book-store/status/[classId]/edit/[editionIndex].vue index 54a884e4e..5d1df0263 100644 --- a/pages/nft-book-store/status/[classId]/edit/[editionIndex].vue +++ b/pages/nft-book-store/status/[classId]/edit/[editionIndex].vue @@ -45,7 +45,21 @@ - + + + + + + + classData?.value?.prices?.length > 1) const price = ref(MINIMAL_PRICE) const stock = ref(1) +const deliverMethod = ref('auto') +const autoMemo = ref('') const nameEn = ref('Standard Edition') const nameZh = ref('標準版') const descriptionEn = ref('') @@ -226,6 +243,7 @@ const shippingRates = ref([{ const hasMultipleShippingRates = computed(() => shippingRates.value.length > 1) const priceItemLabel = computed(() => hasMultiplePrices.value ? 'edition' : 'book') +const isAutoDeliver = computed(() => deliverMethod.value === 'auto') const toolbarOptions = ref([ 'bold', @@ -276,6 +294,8 @@ onMounted(async () => { if (currentEdition) { price.value = currentEdition.price || 0 stock.value = currentEdition.stock || 0 + deliverMethod.value = currentEdition.isAutoDeliver ? 'auto' : 'manual' + autoMemo.value = currentEdition.autoMemo || '' nameEn.value = currentEdition.name?.en || currentEdition.name || '' nameZh.value = currentEdition.name?.zh || currentEdition.name || '' const legacyDescription = typeof currentEdition.description === 'string' ? currentEdition.description : undefined @@ -351,6 +371,8 @@ async function handleSubmit () { priceInDecimal: Math.round(Number(price.value) * 100), price: Number(price.value), stock: Number(stock.value), + isAutoDeliver: isAutoDeliver.value, + autoMemo: autoMemo.value || '', hasShipping: hasShipping.value || false } diff --git a/pages/nft-book-store/status/[classId]/edit/new.vue b/pages/nft-book-store/status/[classId]/edit/new.vue index ae998a27c..4bef21db4 100644 --- a/pages/nft-book-store/status/[classId]/edit/new.vue +++ b/pages/nft-book-store/status/[classId]/edit/new.vue @@ -45,7 +45,20 @@ - + + + + + + + classData?.value?.prices?.length > 1) const price = ref(MINIMAL_PRICE) const stock = ref(1) +const deliverMethod = ref('auto') +const autoMemo = ref('') const nameEn = ref('Standard Edition') const nameZh = ref('標準版') const descriptionEn = ref('') @@ -338,6 +356,8 @@ async function handleSubmit () { priceInDecimal: Math.round(Number(price.value) * 100), price: Number(price.value), stock: Number(stock.value), + isAutoDeliver: deliverMethod.value === 'auto', + autoMemo: deliverMethod.value === 'auto' ? (autoMemo.value || '') : '', hasShipping: hasShipping.value || false } @@ -362,8 +382,25 @@ async function handleSubmit () { isLoading.value = true + let autoDeliverNFTsTxHash = '' + if (editedPrice.isAutoDeliver && editedPrice.stock > 0) { + if (!wallet.value || !signer.value) { + await connect() + } + if (!wallet.value || !signer.value) { + throw new Error('Unable to connect to wallet') + } + autoDeliverNFTsTxHash = await sendNFTsToAPIWallet( + classId.value as string, + editedPrice.stock, + signer.value, + wallet.value + ) + } + await bookStoreApiStore.addEditionPrice(classId.value as string, priceIndex.value, { - price: editedPrice + price: editedPrice, + autoDeliverNFTsTxHash }) router.push({ diff --git a/utils/cosmos.ts b/utils/cosmos.ts index 3f4ad741b..9edc41324 100644 --- a/utils/cosmos.ts +++ b/utils/cosmos.ts @@ -9,7 +9,7 @@ import { parseAuthzGrant } from '@likecoin/iscn-js/dist/messages/parsing' import { GenericAuthorization } from 'cosmjs-types/cosmos/authz/v1beta1/authz' import { formatMsgSend } from '@likecoin/iscn-js/dist/messages/likenft' import { addParamToUrl } from '.' -import { RPC_URL, LIKER_NFT_FEE_WALLET } from '~/constant' +import { RPC_URL, LIKER_NFT_FEE_WALLET, LIKER_NFT_TARGET_ADDRESS } from '~/constant' import network from '~/constant/network' const DEFAULT_GAS_AMOUNT = 200000 @@ -233,26 +233,6 @@ export async function signMintNFT ( return res } -export async function signSendNFT ( - targetAddress: string, - classId: string, - nftId: string, - signer: OfflineSigner, - address: string, - memo?: string -) { - const signingClient = await getSigningClient() - await signingClient.connectWithSigner(RPC_URL, signer) - const res = await signingClient.sendNFTs( - address, - targetAddress, - classId, - [nftId], - { memo } - ) as DeliverTxResponse - return res -} - export async function signSendNFTs ( targetAddress: string, classIds: string[], @@ -342,3 +322,42 @@ export function shortenWalletAddress (address: string) { if (!address) { return '-' } return `${address.slice(0, 10)}...${address.slice(-6)}` } + +export async function sendNFTsToAPIWallet ( + classId: string, + nftCount: number, + signer: OfflineSigner, + ownerAddress: string +) { + if (nftCount <= 0) { return '' } + + if (!ownerAddress) { + throw new Error('Missing owner address') + } + + if (!signer) { + throw new Error('Missing signer') + } + + const { nfts } = await getNFTs({ + classId, + owner: ownerAddress, + needCount: nftCount + }) + const nftIds = nfts.map(nft => nft.id).slice(0, nftCount) + const classIds = nftIds.map(_ => classId) + + const { transactionHash, code } = await signSendNFTs( + LIKER_NFT_TARGET_ADDRESS, + classIds, + nftIds, + signer, + ownerAddress, + 'Send auto delivering NFT Book to API wallet' + ) + + if (!transactionHash || code !== 0) { + throw new Error('Failed to sign and send NFTs') + } + return transactionHash +} diff --git a/utils/index.ts b/utils/index.ts index bdcb43408..7f5ab4826 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -97,3 +97,14 @@ function convertArrayOfObjectsToCSV (data: Record[]): string { export function getPortfolioURL (wallet: string) { return `https://${LIKER_LAND_HOST}/${wallet}` } + +export const deliverMethodOptions = [ + { + value: 'auto', + label: 'Automatic deliver NFT' + }, + { + value: 'manual', + label: 'Sign memo and manually deliver each NFT' + } +]