Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mint authorization #630

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,67 @@ describe('when authorization type is ALLOWANCE', () => {
})
})

describe('when authorization type is MINT', () => {
let screen: RenderResult
describe('basic rendering', () => {
beforeEach(() => {
screen = renderAuthorizationModal({
authorizationType: AuthorizationType.MINT
})
})

it('should render two steps', () => {
expect(screen.getByTestId('multi-step').children.length).toBe(2)
})

it('should render authorization step', () => {
expect(
screen.getByRole('button', {
name: t('@dapps.authorization_modal.authorize_item.action')
})
).toBeInTheDocument()
})

it('should render confirm action step', () => {
expect(
screen.getByRole('button', {
name: t('@dapps.authorization_modal.confirm_transaction.action')
})
).toBeInTheDocument()
})

it('should show only first step enabled', () => {
expect(
screen.getByRole('button', {
name: t('@dapps.authorization_modal.authorize_item.action')
})
).toBeEnabled()

expect(
screen.getByRole('button', {
name: t('@dapps.authorization_modal.confirm_transaction.action')
})
).toBeDisabled()
})
})

describe('when clicking on authorize button', () => {
it('should call onGrant callback', async () => {
const onGrantMock = jest.fn()
const screen = renderAuthorizationModal({
authorizationType: AuthorizationType.APPROVAL,
onGrant: onGrantMock
})
await userEvent.click(
screen.getByRole('button', {
name: t('@dapps.authorization_modal.authorize_nft.action')
})
)
expect(onGrantMock).toHaveBeenCalled()
})
})
})

describe('when clicking revoke authorization button', () => {
let onRevokeMock: jest.Mock
let trackMock: jest.Mock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export function AuthorizationModal({
confirmationStatus,
confirmationError,
authorizedContractLabel,
targetContractLabel,
shouldReauthorize,
currentStep,
error,
Expand Down
31 changes: 29 additions & 2 deletions src/containers/withAuthorizedAction/AuthorizationModal/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export function getStepStatus(
allowance.toString()
)
}
} else {
} else if (authorization.type === AuthorizationType.APPROVAL) {
isDone = hasAuthorization(authorizations, authorization)
}

Expand Down Expand Up @@ -278,9 +278,36 @@ export function getSteps({
]
}

if (authorizationType === AuthorizationType.APPROVAL) {
return [
{
title: getTranslation(translationKeys, 'authorize_nft.title', {
contract: () => (
<TransactionLink
address={authorization.authorizedAddress || ''}
chainId={authorization.chainId}
txHash=""
>
{authorizedContractLabel || authorization.authorizedAddress}
</TransactionLink>
),
targetContract: () => (
<TransactionLink
address={authorization.contractAddress || ''}
chainId={authorization.chainId}
txHash=""
>
{targetContractLabel || authorization.contractAddress}
</TransactionLink>
)
}),
actionType: AuthorizationStepAction.GRANT
}
]
}
return [
{
title: getTranslation(translationKeys, 'authorize_nft.title', {
title: getTranslation(translationKeys, 'authorize_item.title', {
contract: () => (
<TransactionLink
address={authorization.authorizedAddress || ''}
Expand Down
33 changes: 32 additions & 1 deletion src/containers/withAuthorizedAction/withAuthorizedAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AuthorizationType
} from '../../modules/authorization/types'
import {
getCollectionV2ContractInstance,
getERC20ContractInstance,
getERC721ContractInstance
} from '../../modules/authorization/utils'
Expand Down Expand Up @@ -118,7 +119,7 @@ export default function withAuthorizedAction<
getConfirmationStatus,
getConfirmationError
})
} else {
} else if (authorizationType === AuthorizationType.APPROVAL) {
const contract = getERC721ContractInstance(
targetContract.address,
provider
Expand Down Expand Up @@ -148,7 +149,37 @@ export default function withAuthorizedAction<
getConfirmationStatus,
getConfirmationError
})
} else {
const contract = getCollectionV2ContractInstance(
targetContract.address,
provider
)
const isMintingAllowed = await contract.globalMinters(
authorizedAddress
)

const { targetContractLabel } = authorizeOptions

if (isMintingAllowed) {
onAuthorized(true)
setIsLoadingAuthorization(false)
return
}

setAuthModalData({
translationKeys,
authorization,
authorizationType: authorizationType,
action,
network: targetContract.network,
targetContractLabel: targetContractLabel,
authorizedContractLabel,
onAuthorized: () => onAuthorized(false),
getConfirmationStatus,
getConfirmationError
})
}

setShowAuthorizationModal(true)
} catch (error) {
// TODO: handle error scenario
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ type AllowanceOptions = AuthorizeBaseOptions & {
requiredAllowanceInWei: string
}

export type AuthorizeActionOptions = ApprovalOptions | AllowanceOptions
type MintOptions = AuthorizeBaseOptions & {
authorizationType: AuthorizationType.MINT
targetContractLabel?: string
}

export type AuthorizeActionOptions =
| ApprovalOptions
| AllowanceOptions
| MintOptions

type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
Expand Down
27 changes: 26 additions & 1 deletion src/modules/authorization/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getTransactionHashFromAction, waitForTx } from '../transaction/utils'
import { getAnalytics } from '../analytics/utils'
import {
AuthorizationError,
getCollectionV2ContractInstance,
getTokenAmountToApprove,
hasAuthorization,
hasAuthorizationAndEnoughAllowance,
Expand Down Expand Up @@ -141,6 +142,24 @@ export function createAuthorizationSaga() {
})
)
break
case AuthorizationType.MINT:
const collectionContract = getCollectionV2ContractInstance(
authorization.contractAddress,
multicallProviders[chainId]
)

promises.push(
collectionContract
.globalMinters(authorization.authorizedAddress)
.then((isMinter: boolean) => [
authorization,
isMinter ? authorization : null
])
.catch((error: Error) => {
console.error(`Error fetching minters`, authorization, error)
return [authorization, null]
})
)
}
}

Expand Down Expand Up @@ -270,7 +289,8 @@ export function createAuthorizationSaga() {
}

if (
authorization.type === AuthorizationType.APPROVAL &&
(authorization.type === AuthorizationType.APPROVAL ||
authorization.type === AuthorizationType.MINT) &&
!hasAuthorization(authorizations, authorization)
) {
throw new Error(AuthorizationError.GRANT_FAILED)
Expand Down Expand Up @@ -310,6 +330,11 @@ export function createAuthorizationSaga() {
return sendTransaction(contract, erc712 =>
erc712.setApprovalForAll(authorization.authorizedAddress, isApproved)
)
case AuthorizationType.MINT:
const isMinter = action === AuthorizationAction.GRANT
return sendTransaction(contract, collection =>
collection.setMinters([authorization.authorizedAddress], [isMinter])
)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/modules/authorization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { ContractName } from 'decentraland-transactions'

export enum AuthorizationType {
ALLOWANCE = 'allowance',
APPROVAL = 'approval'
APPROVAL = 'approval',
MINT = 'mint'
}

export enum AuthorizationAction {
Expand Down
11 changes: 11 additions & 0 deletions src/modules/authorization/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,14 @@ export function getERC721ContractInstance(
provider
)
}

export function getCollectionV2ContractInstance(
contractAddress: string,
provider: Provider
) {
return new ethers.Contract(
contractAddress,
['function globalMinters(address) view returns (bool)'],
provider
)
}
4 changes: 4 additions & 0 deletions src/modules/translation/defaults/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@
"title": "First, authorize the <contract></contract> contract to operate your <targetContract></targetContract> NFTs on your behalf in Decentraland",
"action": "Authorize"
},
"authorize_item": {
"title": "First, authorize the <contract></contract> contract to mint items from your collection <targetContract></targetContract> in Decentraland",
"action": "Authorize"
},
"confirm_transaction": {
"title": "Confirm transaction to {action} the item",
"action": "Confirm transaction"
Expand Down
6 changes: 5 additions & 1 deletion src/modules/translation/defaults/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,11 @@
"message": "Recuerde establecer un límite igual al precio del artículo {price} MANA o superior."
},
"authorize_nft": {
"title": "Primero, autorice el contrato <Cract> </Contract> para operar NFT en su nombre en Decentraland",
"title": "Primero, autorice el contrato <contract></contract> para operar NFT en su nombre en Decentraland",
"action": "Autorizar"
},
"authorize_item": {
"title": "Primero, autorice el contrato <contract></contract> a mintear NFTs desde su colección <targetContract></targetContract> en Decentraland",
"action": "Autorizar"
},
"confirm_transaction": {
Expand Down
4 changes: 4 additions & 0 deletions src/modules/translation/defaults/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@
"title": "Tout d'abord, autorisez le <contract></contract> Contrat pour exploiter NFT en votre nom à Decentraland",
"action": "Autoriser"
},
"authorize_item": {
"title": "Tout d'abord, autorisez le <contract></contract> Contrat avec les articles de la menthe de votre collection <targetContract></targetContract> en décentral et",
"action": "Authorize"
},
"confirm_transaction": {
"title": "Confirmer la transaction à {action}l'articlem",
"action": "Confirmer la transaction"
Expand Down
4 changes: 4 additions & 0 deletions src/modules/translation/defaults/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@
"title": "まず、<契約> </契約>契約を承認して、Decentralandでお客様に代わってNFTを操作する",
"action": "許可"
},
"authorize_item": {
"title": "まず、を認可します<contract></contract> コレクションのアイテムをミントする契約<targetContract></targetContract> Decentralandで",
"action": "許可"
},
"confirm_transaction": {
"title": "{action}アイテムへのトランザクションを確認します",
"action": "トランザクションを確認します"
Expand Down
4 changes: 4 additions & 0 deletions src/modules/translation/defaults/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@
"title": "먼저 승인하십시오<contract></contract> Decentraland에서 귀하를 대신하여 NFT를 운영하는 계약",
"action": "승인"
},
"authorize_item": {
"title": "먼저 승인하십시오 <contract></contract> 컬렉션에서 Mint 품목에 계약을 체결하십시오 <targetContract></targetContract> Decentraland에서",
"action": "승인"
},
"confirm_transaction": {
"title": "{action} 항목에 대한 트랜잭션을 확인하십시오",
"action": "거래 확인"
Expand Down
4 changes: 4 additions & 0 deletions src/modules/translation/defaults/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@
"title": "首先,授权 <contract></contract> 代表您在去居中的NFT的合同",
"action": "授权"
},
"authorize_item": {
"title": "首先,授权 <contract></contract> 与您收藏的薄荷商品合同 <targetContract></targetContract> 在分散",
"action": "授权"
},
"confirm_transaction": {
"title": "确认交易到 {action} 项目",
"action": "确认交易"
Expand Down
Loading