Merge pull request #163 from public-awesome/v2-migration
Migration update for mutable v2 collections
This commit is contained in:
commit
43afca29e8
@ -1,4 +1,4 @@
|
|||||||
APP_VERSION=0.6.1
|
APP_VERSION=0.6.2
|
||||||
|
|
||||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||||
NEXT_PUBLIC_SG721_CODE_ID=2092
|
NEXT_PUBLIC_SG721_CODE_ID=2092
|
||||||
@ -8,8 +8,8 @@ NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID=2080
|
|||||||
NEXT_PUBLIC_BASE_MINTER_CODE_ID=1910
|
NEXT_PUBLIC_BASE_MINTER_CODE_ID=1910
|
||||||
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1ukaladct74um4lhn6eru0d9hdrcqzj3q8sjrfcg7226xey0xc2gsy0gl22"
|
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1ukaladct74um4lhn6eru0d9hdrcqzj3q8sjrfcg7226xey0xc2gsy0gl22"
|
||||||
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1fnfywcnzzwledr93at65qm8gf953tjxgh6u2u4r8n9vsdv7u75eqe7ecn3"
|
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1fnfywcnzzwledr93at65qm8gf953tjxgh6u2u4r8n9vsdv7u75eqe7ecn3"
|
||||||
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1l5v0vgly8r9jw4exeajnftymr29kn70n23gpl2g5fylaww2pzkhq0rks7c"
|
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1gy6hr9sq9fzrykzw0emmehnjy27agreuepjrfnjnlwlugg29l2qqt0yu2j"
|
||||||
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars10rmaxgnjvskuumgv7e2awqkhdqdcygkwrz8a8vvt88szj7fc7xlq5jcs3f"
|
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars18kzfpdgx36m95mszchegnk7car4sq03uvg25zeph2j7xg3rk03cs007sxr"
|
||||||
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars13pw8r33dsnghlxfj2upaywf38z2fc6npuw9maq9e5cpet4v285sscgzjp2"
|
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars13pw8r33dsnghlxfj2upaywf38z2fc6npuw9maq9e5cpet4v285sscgzjp2"
|
||||||
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
||||||
NEXT_PUBLIC_WHITELIST_CODE_ID=2093
|
NEXT_PUBLIC_WHITELIST_CODE_ID=2093
|
||||||
|
@ -35,6 +35,7 @@ export const ACTION_TYPES = [
|
|||||||
'update_token_metadata',
|
'update_token_metadata',
|
||||||
'batch_update_token_metadata',
|
'batch_update_token_metadata',
|
||||||
'freeze_token_metadata',
|
'freeze_token_metadata',
|
||||||
|
'enable_updatable',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export interface ActionListItem {
|
export interface ActionListItem {
|
||||||
@ -205,6 +206,11 @@ export const SG721_UPDATABLE_ACTION_LIST: ActionListItem[] = [
|
|||||||
name: 'Freeze Token Metadata',
|
name: 'Freeze Token Metadata',
|
||||||
description: `Render the metadata for tokens no longer updatable`,
|
description: `Render the metadata for tokens no longer updatable`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'enable_updatable',
|
||||||
|
name: 'Enable Updatable',
|
||||||
|
description: `Render a collection updatable following a migration`,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export interface DispatchExecuteProps {
|
export interface DispatchExecuteProps {
|
||||||
@ -212,43 +218,28 @@ export interface DispatchExecuteProps {
|
|||||||
[k: string]: unknown
|
[k: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
type Select<T extends ActionType> = T
|
|
||||||
|
|
||||||
/** @see {@link VendingMinterInstance}{@link BaseMinterInstance} */
|
/** @see {@link VendingMinterInstance}{@link BaseMinterInstance} */
|
||||||
export type DispatchExecuteArgs = {
|
export interface DispatchExecuteArgs {
|
||||||
minterContract: string
|
minterContract: string
|
||||||
sg721Contract: string
|
sg721Contract: string
|
||||||
vendingMinterMessages?: VendingMinterInstance
|
vendingMinterMessages?: VendingMinterInstance
|
||||||
baseMinterMessages?: BaseMinterInstance
|
baseMinterMessages?: BaseMinterInstance
|
||||||
sg721Messages?: SG721Instance
|
sg721Messages?: SG721Instance
|
||||||
txSigner: string
|
txSigner: string
|
||||||
} & (
|
type: string | undefined
|
||||||
| { type: undefined }
|
tokenUri: string
|
||||||
| { type: Select<'mint_token_uri'>; tokenUri: string }
|
price: string
|
||||||
| { type: Select<'update_mint_price'>; price: string }
|
recipient: string
|
||||||
| { type: Select<'update_discount_price'>; price: string }
|
tokenId: number
|
||||||
| { type: Select<'remove_discount_price'> }
|
batchNumber: number
|
||||||
| { type: Select<'mint_to'>; recipient: string }
|
whitelist: string
|
||||||
| { type: Select<'mint_for'>; recipient: string; tokenId: number }
|
startTime: string | undefined
|
||||||
| { type: Select<'batch_mint'>; recipient: string; batchNumber: number }
|
limit: number
|
||||||
| { type: Select<'set_whitelist'>; whitelist: string }
|
tokenIds: string
|
||||||
| { type: Select<'update_start_time'>; startTime: string }
|
recipients: string[]
|
||||||
| { type: Select<'update_start_trading_time'>; startTime?: string }
|
collectionInfo: CollectionInfo | undefined
|
||||||
| { type: Select<'update_per_address_limit'>; limit: number }
|
baseUri: string
|
||||||
| { type: Select<'shuffle'> }
|
}
|
||||||
| { type: Select<'transfer'>; recipient: string; tokenId: number }
|
|
||||||
| { type: Select<'batch_transfer'>; recipient: string; tokenIds: string }
|
|
||||||
| { type: Select<'burn'>; tokenId: number }
|
|
||||||
| { type: Select<'batch_burn'>; tokenIds: string }
|
|
||||||
| { type: Select<'batch_mint_for'>; recipient: string; tokenIds: string }
|
|
||||||
| { type: Select<'airdrop'>; recipients: string[] }
|
|
||||||
| { type: Select<'burn_remaining'> }
|
|
||||||
| { type: Select<'update_collection_info'>; collectionInfo: CollectionInfo | undefined }
|
|
||||||
| { type: Select<'freeze_collection_info'> }
|
|
||||||
| { type: Select<'update_token_metadata'>; tokenId: number; tokenUri: string }
|
|
||||||
| { type: Select<'batch_update_token_metadata'>; tokenIds: string; baseUri: string }
|
|
||||||
| { type: Select<'freeze_token_metadata'> }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||||
const { vendingMinterMessages, baseMinterMessages, sg721Messages, txSigner } = args
|
const { vendingMinterMessages, baseMinterMessages, sg721Messages, txSigner } = args
|
||||||
@ -281,7 +272,7 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
|||||||
return vendingMinterMessages.setWhitelist(txSigner, args.whitelist)
|
return vendingMinterMessages.setWhitelist(txSigner, args.whitelist)
|
||||||
}
|
}
|
||||||
case 'update_start_time': {
|
case 'update_start_time': {
|
||||||
return vendingMinterMessages.updateStartTime(txSigner, args.startTime)
|
return vendingMinterMessages.updateStartTime(txSigner, args.startTime as string)
|
||||||
}
|
}
|
||||||
case 'update_start_trading_time': {
|
case 'update_start_trading_time': {
|
||||||
return vendingMinterMessages.updateStartTradingTime(txSigner, args.startTime)
|
return vendingMinterMessages.updateStartTradingTime(txSigner, args.startTime)
|
||||||
@ -304,6 +295,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
|||||||
case 'freeze_token_metadata': {
|
case 'freeze_token_metadata': {
|
||||||
return sg721Messages.freezeTokenMetadata()
|
return sg721Messages.freezeTokenMetadata()
|
||||||
}
|
}
|
||||||
|
case 'enable_updatable': {
|
||||||
|
return sg721Messages.enableUpdatable()
|
||||||
|
}
|
||||||
case 'shuffle': {
|
case 'shuffle': {
|
||||||
return vendingMinterMessages.shuffle(txSigner)
|
return vendingMinterMessages.shuffle(txSigner)
|
||||||
}
|
}
|
||||||
@ -368,7 +362,7 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
|||||||
return vendingMinterMessages(minterContract)?.setWhitelist(args.whitelist)
|
return vendingMinterMessages(minterContract)?.setWhitelist(args.whitelist)
|
||||||
}
|
}
|
||||||
case 'update_start_time': {
|
case 'update_start_time': {
|
||||||
return vendingMinterMessages(minterContract)?.updateStartTime(args.startTime)
|
return vendingMinterMessages(minterContract)?.updateStartTime(args.startTime as string)
|
||||||
}
|
}
|
||||||
case 'update_start_trading_time': {
|
case 'update_start_trading_time': {
|
||||||
return vendingMinterMessages(minterContract)?.updateStartTradingTime(args.startTime as string)
|
return vendingMinterMessages(minterContract)?.updateStartTradingTime(args.startTime as string)
|
||||||
@ -391,6 +385,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
|||||||
case 'freeze_token_metadata': {
|
case 'freeze_token_metadata': {
|
||||||
return sg721Messages(sg721Contract)?.freezeTokenMetadata()
|
return sg721Messages(sg721Contract)?.freezeTokenMetadata()
|
||||||
}
|
}
|
||||||
|
case 'enable_updatable': {
|
||||||
|
return sg721Messages(sg721Contract)?.enableUpdatable()
|
||||||
|
}
|
||||||
case 'shuffle': {
|
case 'shuffle': {
|
||||||
return vendingMinterMessages(minterContract)?.shuffle()
|
return vendingMinterMessages(minterContract)?.shuffle()
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ export interface SG721Instance {
|
|||||||
updateTokenMetadata: (tokenId: string, tokenURI: string) => Promise<string>
|
updateTokenMetadata: (tokenId: string, tokenURI: string) => Promise<string>
|
||||||
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string) => Promise<string>
|
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string) => Promise<string>
|
||||||
freezeTokenMetadata: () => Promise<string>
|
freezeTokenMetadata: () => Promise<string>
|
||||||
|
enableUpdatable: () => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Sg721Messages {
|
export interface Sg721Messages {
|
||||||
@ -107,6 +108,7 @@ export interface Sg721Messages {
|
|||||||
updateTokenMetadata: (tokenId: string, tokenURI: string) => UpdateTokenMetadataMessage
|
updateTokenMetadata: (tokenId: string, tokenURI: string) => UpdateTokenMetadataMessage
|
||||||
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string) => BatchUpdateTokenMetadataMessage
|
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string) => BatchUpdateTokenMetadataMessage
|
||||||
freezeTokenMetadata: () => FreezeTokenMetadataMessage
|
freezeTokenMetadata: () => FreezeTokenMetadataMessage
|
||||||
|
enableUpdatable: () => EnableUpdatableMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransferNFTMessage {
|
export interface TransferNFTMessage {
|
||||||
@ -247,6 +249,13 @@ export interface FreezeTokenMetadataMessage {
|
|||||||
funds: Coin[]
|
funds: Coin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnableUpdatableMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: { enable_updatable: Record<string, never> }
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateCollectionInfoMessage {
|
export interface UpdateCollectionInfoMessage {
|
||||||
sender: string
|
sender: string
|
||||||
contract: string
|
contract: string
|
||||||
@ -687,6 +696,20 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
|||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enableUpdatable = async (): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
txSigner,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
enable_updatable: {},
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
'',
|
||||||
|
[coin('500000000', 'ustars')],
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractAddress,
|
contractAddress,
|
||||||
ownerOf,
|
ownerOf,
|
||||||
@ -716,6 +739,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
|||||||
updateTokenMetadata,
|
updateTokenMetadata,
|
||||||
batchUpdateTokenMetadata,
|
batchUpdateTokenMetadata,
|
||||||
freezeTokenMetadata,
|
freezeTokenMetadata,
|
||||||
|
enableUpdatable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -963,6 +987,17 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enableUpdatable = () => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
enable_updatable: {},
|
||||||
|
},
|
||||||
|
funds: [coin('500000000', 'ustars')],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updateCollectionInfo = (collectionInfo: CollectionInfo) => {
|
const updateCollectionInfo = (collectionInfo: CollectionInfo) => {
|
||||||
return {
|
return {
|
||||||
sender: txSigner,
|
sender: txSigner,
|
||||||
@ -1001,6 +1036,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
|||||||
updateTokenMetadata,
|
updateTokenMetadata,
|
||||||
batchUpdateTokenMetadata,
|
batchUpdateTokenMetadata,
|
||||||
freezeTokenMetadata,
|
freezeTokenMetadata,
|
||||||
|
enableUpdatable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stargaze-studio",
|
"name": "stargaze-studio",
|
||||||
"version": "0.6.1",
|
"version": "0.6.2",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
@ -976,31 +977,56 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
const client = wallet.client
|
const client = wallet.client
|
||||||
if (!client) return
|
if (!client) return
|
||||||
if (BASE_FACTORY_ADDRESS) {
|
if (BASE_FACTORY_ADDRESS) {
|
||||||
const baseFactoryParameters = await client.queryContractSmart(BASE_FACTORY_ADDRESS, { params: {} })
|
const baseFactoryParameters = await client
|
||||||
|
.queryContractSmart(BASE_FACTORY_ADDRESS, { params: {} })
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||||
|
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||||
|
})
|
||||||
setBaseMinterCreationFee(baseFactoryParameters?.params?.creation_fee?.amount)
|
setBaseMinterCreationFee(baseFactoryParameters?.params?.creation_fee?.amount)
|
||||||
}
|
}
|
||||||
if (BASE_FACTORY_UPDATABLE_ADDRESS) {
|
if (BASE_FACTORY_UPDATABLE_ADDRESS) {
|
||||||
const baseFactoryUpdatableParameters = await client.queryContractSmart(BASE_FACTORY_UPDATABLE_ADDRESS, {
|
const baseFactoryUpdatableParameters = await client
|
||||||
params: {},
|
.queryContractSmart(BASE_FACTORY_UPDATABLE_ADDRESS, {
|
||||||
})
|
params: {},
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||||
|
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||||
|
})
|
||||||
setBaseMinterUpdatableCreationFee(baseFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
setBaseMinterUpdatableCreationFee(baseFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
||||||
}
|
}
|
||||||
if (VENDING_FACTORY_ADDRESS) {
|
if (VENDING_FACTORY_ADDRESS) {
|
||||||
const vendingFactoryParameters = await client.queryContractSmart(VENDING_FACTORY_ADDRESS, { params: {} })
|
const vendingFactoryParameters = await client
|
||||||
|
.queryContractSmart(VENDING_FACTORY_ADDRESS, { params: {} })
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||||
|
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||||
|
})
|
||||||
setVendingMinterCreationFee(vendingFactoryParameters?.params?.creation_fee?.amount)
|
setVendingMinterCreationFee(vendingFactoryParameters?.params?.creation_fee?.amount)
|
||||||
setMinimumMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount)
|
setMinimumMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount)
|
||||||
}
|
}
|
||||||
if (VENDING_FACTORY_UPDATABLE_ADDRESS) {
|
if (VENDING_FACTORY_UPDATABLE_ADDRESS) {
|
||||||
const vendingFactoryUpdatableParameters = await client.queryContractSmart(VENDING_FACTORY_UPDATABLE_ADDRESS, {
|
const vendingFactoryUpdatableParameters = await client
|
||||||
params: {},
|
.queryContractSmart(VENDING_FACTORY_UPDATABLE_ADDRESS, {
|
||||||
})
|
params: {},
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||||
|
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||||
|
})
|
||||||
setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
||||||
setMinimumUpdatableMintPrice(vendingFactoryUpdatableParameters?.params?.min_mint_price?.amount)
|
setMinimumUpdatableMintPrice(vendingFactoryUpdatableParameters?.params?.min_mint_price?.amount)
|
||||||
}
|
}
|
||||||
if (VENDING_FACTORY_FLEX_ADDRESS) {
|
if (VENDING_FACTORY_FLEX_ADDRESS) {
|
||||||
const vendingFactoryFlexParameters = await client.queryContractSmart(VENDING_FACTORY_FLEX_ADDRESS, {
|
const vendingFactoryFlexParameters = await client
|
||||||
params: {},
|
.queryContractSmart(VENDING_FACTORY_FLEX_ADDRESS, {
|
||||||
})
|
params: {},
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||||
|
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||||
|
})
|
||||||
setVendingMinterFlexCreationFee(vendingFactoryFlexParameters?.params?.creation_fee?.amount)
|
setVendingMinterFlexCreationFee(vendingFactoryFlexParameters?.params?.creation_fee?.amount)
|
||||||
setMinimumFlexMintPrice(vendingFactoryFlexParameters?.params?.min_mint_price?.amount)
|
setMinimumFlexMintPrice(vendingFactoryFlexParameters?.params?.min_mint_price?.amount)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ const CollectionList: NextPage = () => {
|
|||||||
.then((contractType) => {
|
.then((contractType) => {
|
||||||
if (contractType?.includes('sg-base-minter')) {
|
if (contractType?.includes('sg-base-minter')) {
|
||||||
setMyOneOfOneCollections((prevState) => [...prevState, collection])
|
setMyOneOfOneCollections((prevState) => [...prevState, collection])
|
||||||
} else if (contractType?.includes('sg-minter')) {
|
} else if (contractType?.includes('sg-minter') || contractType?.includes('flex')) {
|
||||||
setMyStandardCollections((prevState) => [...prevState, collection])
|
setMyStandardCollections((prevState) => [...prevState, collection])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user