Skip to content

Commit

Permalink
Merge pull request public-awesome#424 from public-awesome/token-merge…
Browse files Browse the repository at this point in the history
…-minter

Token Merge / Burn-to-Mint Collection Support
  • Loading branch information
MightOfOaks authored Nov 21, 2024
2 parents 0391666 + 780ceed commit aaf47cb
Show file tree
Hide file tree
Showing 23 changed files with 3,664 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS="stars1d57xe77mvcg5q
NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS="stars1e6t6lp052er2gu3rwjnf434vgh59ydkfg8dm589fxlx593afqmuqh75a0s"
NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS="stars1k6ee8qgwvumguqnqqrvsnwluwk0rp994nkcgdemk0tj3ecc5kk8su2tcr4"

NEXT_PUBLIC_TOKEN_MERGE_FACTORY_ADDRESS="stars1h7j7"

NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS="stars1crgx0f70fzksa57hq87wtl8f04h0qyk5la0hk0fu8dyhl67ju80qaxzr5z"
NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS="stars136yp6fl9h66m0cwv8weu4w4aawveuz40992ty0atj5ecjd8z0thqv9xpy5"
Expand Down
4 changes: 2 additions & 2 deletions components/AssetsPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse

return (
<div className="flex flex-col items-center">
<div className="mt-2 w-[400px] h-[300px]">{renderImages()}</div>
<div className="mt-2 ml-36 w-[400px] h-[300px]">{renderImages()}</div>

<div className="mt-5 btn-group">
<div className="mt-5 ml-36 btn-group">
<button className="text-white bg-plumbus-light btn" onClick={multiplePrevPage} type="button">
««
</button>
Expand Down
2 changes: 1 addition & 1 deletion components/collections/actions/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
import type { ActionListItem } from './actions'
import { BASE_ACTION_LIST, OPEN_EDITION_ACTION_LIST, SG721_UPDATABLE_ACTION_LIST, VENDING_ACTION_LIST } from './actions'

export type MinterType = 'base' | 'vending' | 'openEdition'
export type MinterType = 'base' | 'vending' | 'openEdition' | 'token-merge'
export type Sg721Type = 'updatable' | 'base'

export interface ActionsComboboxProps {
Expand Down
33 changes: 33 additions & 0 deletions components/forms/SelectCollection.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useMemo, useState } from 'react'
import { uid } from 'utils/random'

import type { SelectedCollection } from './SelectCollection'

export function useSelectCollectionState() {
const [record, setRecord] = useState<Record<string, SelectedCollection>>(() => ({}))

const entries = useMemo(() => Object.entries(record), [record])
const values = useMemo(() => Object.values(record), [record])

function add(selectedCollection: SelectedCollection = { address: '', amount: 0 }) {
setRecord((prev) => ({ ...prev, [uid()]: selectedCollection }))
}

function update(key: string, selectedCollection = record[key]) {
setRecord((prev) => ({ ...prev, [key]: selectedCollection }))
}

function remove(key: string) {
return setRecord((prev) => {
const latest = { ...prev }
delete latest[key]
return latest
})
}

function reset() {
setRecord({})
}

return { entries, values, add, update, remove, reset }
}
146 changes: 146 additions & 0 deletions components/forms/SelectCollection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { toUtf8 } from '@cosmjs/encoding'
import { FormControl } from 'components/FormControl'
import { AddressInput, NumberInput } from 'components/forms/FormInput'
import { useEffect, useId, useMemo } from 'react'
import toast from 'react-hot-toast'
import { FaMinus, FaPlus } from 'react-icons/fa'
import { SG721_NAME_ADDRESS } from 'utils/constants'
import { isValidAddress } from 'utils/isValidAddress'
import { useWallet } from 'utils/wallet'

import { useInputState, useNumberInputState } from './FormInput.hooks'

export interface SelectedCollection {
address: string
amount: number
}

export interface SelectCollectionProps {
title: string
subtitle?: string
isRequired?: boolean
selectedCollections: [string, SelectedCollection][]
onAdd: () => void
onChange: (key: string, selectedCollection: SelectedCollection) => void
onRemove: (key: string) => void
}

export function SelectCollection(props: SelectCollectionProps) {
const { title, subtitle, isRequired, selectedCollections, onAdd, onChange, onRemove } = props

return (
<FormControl isRequired={isRequired} subtitle={subtitle} title={title}>
{selectedCollections.map(([id], i) => (
<SelectCollectionItem
key={`sc-${id}`}
defaultSelectedCollection={selectedCollections[i][1]}
id={id}
isLast={i === selectedCollections.length - 1}
onAdd={onAdd}
onChange={onChange}
onRemove={onRemove}
/>
))}
</FormControl>
)
}

export interface SelectCollectionItemProps {
id: string
isLast: boolean
onAdd: SelectCollectionProps['onAdd']
onChange: SelectCollectionProps['onChange']
onRemove: SelectCollectionProps['onRemove']
defaultSelectedCollection: SelectedCollection
}

export function SelectCollectionItem({
id,
isLast,
onAdd,
onChange,
onRemove,
defaultSelectedCollection,
}: SelectCollectionItemProps) {
const wallet = useWallet()
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])

const htmlId = useId()

const addressState = useInputState({
id: `ma-address-${htmlId}`,
name: `ma-address-${htmlId}`,
title: `Collection Address`,
defaultValue: defaultSelectedCollection.address,
})

const amountState = useNumberInputState({
id: `amount-${htmlId}`,
name: `amount-${htmlId}`,
title: `Amount`,
defaultValue: defaultSelectedCollection.amount,
})

useEffect(() => {
onChange(id, { address: addressState.value, amount: amountState.value })
}, [addressState.value, amountState.value, id])

const resolveAddress = async (name: string) => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
Buffer.from(
`0006${Buffer.from('tokens').toString('hex')}${Buffer.from(name).toString('hex')}`,
'hex',
).toString(),
),
)
.then((res) => {
const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri
if (tokenUri && isValidAddress(tokenUri)) onChange(id, { address: tokenUri, amount: amountState.value })
else {
toast.error(`Resolved address is empty or invalid for the name: ${name}.stars`)
onChange(id, { address: '', amount: amountState.value })
}
})
.catch((err) => {
toast.error(`Error resolving address for the name: ${name}.stars`)
console.error(err)
onChange(id, { address: '', amount: amountState.value })
})
}
useEffect(() => {
if (addressState.value.endsWith('.stars')) {
void resolveAddress(addressState.value.split('.')[0])
} else {
onChange(id, {
address: addressState.value,
amount: amountState.value,
})
}
}, [addressState.value, id])

return (
<div className="grid relative space-x-2 md:grid-cols-[40%_30%_7%] lg:grid-cols-[65%_24%_7%] 2xl:grid-cols-[70%_20%_7%]">
<AddressInput {...addressState} />
<NumberInput className="ml-2" {...amountState} />

<div className="flex py-2 mt-8 w-8 h-12">
<button
className="flex justify-center items-center p-3 mb-1 bg-stargaze-80 hover:bg-plumbus-60 rounded-full"
onClick={(e) => {
e.preventDefault()
isLast ? onAdd() : onRemove(id)
}}
type="button"
>
<Icon className="w-3 h-3" />
</button>
</div>
</div>
)
}
Loading

0 comments on commit aaf47cb

Please sign in to comment.