Skip to content

Commit

Permalink
feat: add mint authorization (#630)
Browse files Browse the repository at this point in the history
  • Loading branch information
Melisa Anabella Rossi authored Jul 22, 2024
1 parent 04a5a1d commit 1970849
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 6 deletions.
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
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,13 @@ export function getSteps({
]
}

const title =
authorizationType === AuthorizationType.APPROVAL
? 'authorize_nft.title'
: 'authorize_item.title'
return [
{
title: getTranslation(translationKeys, 'authorize_nft.title', {
title: getTranslation(translationKeys, 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

0 comments on commit 1970849

Please sign in to comment.