diff --git a/abi/contracts/booking-token/BookingTokenV2.sol/BookingTokenV2.json b/abi/contracts/booking-token/BookingTokenV2.sol/BookingTokenV2.json new file mode 100644 index 0000000..7db5759 --- /dev/null +++ b/abi/contracts/booking-token/BookingTokenV2.sol/BookingTokenV2.json @@ -0,0 +1,1574 @@ +[ + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ERC1967InvalidImplementation", + "type": "error" + }, + { + "inputs": [], + "name": "ERC1967NonPayable", + "type": "error" + }, + { + "inputs": [], + "name": "ERC721EnumerableForbiddenBatchMint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721IncorrectOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721InsufficientApproval", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC721InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC721InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721InvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC721InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC721InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721NonexistentToken", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "ERC721OutOfBoundsIndex", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minExpirationTimestampDiff", + "type": "uint256" + } + ], + "name": "ExpirationTimestampTooSoon", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationPrice", + "type": "uint256" + } + ], + "name": "IncorrectPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + } + ], + "name": "InsufficientAllowance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "NoActiveCancellationProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToAcceptCancellation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToCancelProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToCounterCancellation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToInitiateCancellation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "NotCMAccount", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + } + ], + "name": "ReservationExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "reservedFor", + "type": "address" + }, + { + "internalType": "address", + "name": "buyer", + "type": "address" + } + ], + "name": "ReservationMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "supplier", + "type": "address" + } + ], + "name": "SupplierIsNotOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "TokenHasActiveCancellationProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "reservedFor", + "type": "address" + } + ], + "name": "TokenIsReserved", + "type": "error" + }, + { + "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_fromTokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_toTokenId", + "type": "uint256" + } + ], + "name": "BatchMetadataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "acceptedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "CancellationAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "counteredBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRefundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRefundCurrency", + "type": "address" + } + ], + "name": "CancellationCountered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "initiatedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "CancellationInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "canceledBy", + "type": "address" + } + ], + "name": "CancellationProposalCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "MetadataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + } + ], + "name": "TokenBought", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "reservedFor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + } + ], + "name": "TokenReserved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_EXPIRATION_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "acceptCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "buyReservedToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "cancelCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newRefundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "newRefundCurrency", + "type": "address" + } + ], + "name": "counterCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getCancellationProposalStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "initiatedBy", + "type": "address" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getManagerAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMinExpirationTimestampDiff", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getReservationPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "manager", + "type": "address" + }, + { + "internalType": "address", + "name": "defaultAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "upgrader", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "initiateCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isCMAccount", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "reservedFor", + "type": "address" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + }, + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + } + ], + "name": "safeMintWithReservation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "manager", + "type": "address" + } + ], + "name": "setManagerAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "minExpirationTimestampDiff", + "type": "uint256" + } + ], + "name": "setMinExpirationTimestampDiff", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/contracts/booking-token/BookingToken.sol b/contracts/booking-token/BookingToken.sol index 461c9b5..381bae3 100644 --- a/contracts/booking-token/BookingToken.sol +++ b/contracts/booking-token/BookingToken.sol @@ -90,7 +90,7 @@ contract BookingToken is bytes32 private constant BookingTokenStorageLocation = 0x9db9d405bf15683ce835607b1f0b423dc1484d44bb9d5af64a483fa4afd82900; - function _getBookingTokenStorage() private pure returns (BookingTokenStorage storage $) { + function _getBookingTokenStorage() internal pure returns (BookingTokenStorage storage $) { assembly { $.slot := BookingTokenStorageLocation } @@ -251,7 +251,7 @@ contract BookingToken is uint256 expirationTimestamp, uint256 price, IERC20 paymentToken - ) public onlyCMAccount(msg.sender) { + ) public virtual onlyCMAccount(msg.sender) { // Require reservedFor to be a CM Account requireCMAccount(reservedFor); @@ -366,7 +366,7 @@ contract BookingToken is /** * @notice Check if the token is transferable */ - function checkTransferable(uint256 tokenId) internal { + function checkTransferable(uint256 tokenId) internal virtual { BookingTokenStorage storage $ = _getBookingTokenStorage(); TokenReservation memory reservation = $._reservations[tokenId]; diff --git a/contracts/booking-token/BookingTokenV2.sol b/contracts/booking-token/BookingTokenV2.sol new file mode 100644 index 0000000..55c43ec --- /dev/null +++ b/contracts/booking-token/BookingTokenV2.sol @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { BookingToken, IERC20 } from "./BookingToken.sol"; + +/** + * @title BookingTokenV2 + * @notice This contract extends BookingToken to add additional functionality. + * Specifically, it introduces a cancellation process after the token is bought. + */ +contract BookingTokenV2 is BookingToken { + /*************************************************** + * EVENTS * + ***************************************************/ + + /** + * @notice Event emitted when a cancellation is initiated. + * + * @param tokenId token id + * @param initiatedBy address that initiated the cancellation + * @param refundAmount proposed refund amount + * @param refundCurrency ERC20 token address for the refund + */ + event CancellationInitiated( + uint256 indexed tokenId, + address indexed initiatedBy, + uint256 refundAmount, + address refundCurrency + ); + + /** + * @notice Event emitted when a cancellation is accepted. + * + * @param tokenId token id + * @param acceptedBy address that accepted the cancellation + * @param refundAmount final refund amount + * @param refundCurrency ERC20 token address for the refund + */ + event CancellationAccepted( + uint256 indexed tokenId, + address indexed acceptedBy, + uint256 refundAmount, + address refundCurrency + ); + + /** + * @notice Event emitted when a cancellation proposal is countered. + * + * @param tokenId token id + * @param counteredBy address that countered the proposal + * @param newRefundAmount new proposed refund amount + * @param newRefundCurrency new proposed ERC20 token address for the refund + */ + event CancellationCountered( + uint256 indexed tokenId, + address indexed counteredBy, + uint256 newRefundAmount, + address newRefundCurrency + ); + + /** + * @notice Event emitted when a cancellation proposal is canceled. + * + * @param tokenId token id + * @param canceledBy address that canceled the proposal + */ + event CancellationProposalCanceled(uint256 indexed tokenId, address indexed canceledBy); + + /*************************************************** + * ERRORS * + ***************************************************/ + + /** + * @notice Error for when the caller is not authorized to initiate a cancellation. + * + * @param caller The address of the caller + */ + error NotAuthorizedToInitiateCancellation(address caller); + + /** + * @notice Error for when there is no active cancellation proposal. + * + * @param tokenId The token id for which there is no active proposal + */ + error NoActiveCancellationProposal(uint256 tokenId); + + /** + * @notice Error for when the caller is not authorized to accept a cancellation. + * + * @param caller The address of the caller + */ + error NotAuthorizedToAcceptCancellation(address caller); + + /** + * @notice Error for when the caller is not authorized to counter a cancellation. + * + * @param caller The address of the caller + */ + error NotAuthorizedToCounterCancellation(address caller); + + /** + * @notice Error for when the caller is not authorized to cancel a proposal. + * + * @param caller The address of the caller + */ + error NotAuthorizedToCancelProposal(address caller); + + /** + * @notice Error for when a token has an active cancellation proposal and cannot be transferred. + * + * @param tokenId The token id that has an active cancellation proposal + */ + error TokenHasActiveCancellationProposal(uint256 tokenId); + + /*************************************************** + * STORAGE * + ***************************************************/ + + // Mapping to store the original supplier (minter) of each token + mapping(uint256 => address) private _originalSupplier; + + // Mapping to store the ongoing cancellation proposals for each token + struct CancellationProposal { + uint256 refundAmount; + address refundCurrency; + address initiatedBy; + bool isActive; + } + mapping(uint256 => CancellationProposal) private _cancellationProposals; + + /*************************************************** + * NEW FUNCTIONS * + ***************************************************/ + + /** + * @notice Mints a new token with a reservation for a specific address. + * + * @param reservedFor The CM Account address that can buy the token + * @param uri The URI of the token + * @param expirationTimestamp The expiration timestamp + * @param price The price of the token + * @param paymentToken The token used to pay for the reservation. If address(0) then native. + */ + function safeMintWithReservation( + address reservedFor, + string memory uri, + uint256 expirationTimestamp, + uint256 price, + IERC20 paymentToken + ) public override onlyCMAccount(msg.sender) { + super.safeMintWithReservation(reservedFor, uri, expirationTimestamp, price, paymentToken); + uint256 tokenId = _getBookingTokenStorage()._nextTokenId - 1; + _originalSupplier[tokenId] = msg.sender; + } + + /** + * @notice Initiates a cancellation for a bought token. + * + * @param tokenId The token id to initiate the cancellation for + * @param refundAmount The proposed refund amount in wei + * @param refundCurrency The ERC20 token address for the refund + */ + function initiateCancellation(uint256 tokenId, uint256 refundAmount, address refundCurrency) external { + address owner = _requireOwned(tokenId); + address supplier = _originalSupplier[tokenId]; + if (msg.sender != owner && msg.sender != supplier) { + revert NotAuthorizedToInitiateCancellation(msg.sender); + } + + _cancellationProposals[tokenId] = CancellationProposal({ + refundAmount: refundAmount, + refundCurrency: refundCurrency, + initiatedBy: msg.sender, + isActive: true + }); + + emit CancellationInitiated(tokenId, msg.sender, refundAmount, refundCurrency); + } + + /** + * @notice Accepts a cancellation proposal for a bought token. + * + * @param tokenId The token id to accept the cancellation for + */ + function acceptCancellation(uint256 tokenId) external { + address owner = _requireOwned(tokenId); + CancellationProposal memory proposal = _cancellationProposals[tokenId]; + if (!proposal.isActive) { + revert NoActiveCancellationProposal(tokenId); + } + + address supplier = _originalSupplier[tokenId]; + if (msg.sender == proposal.initiatedBy) { + revert NotAuthorizedToAcceptCancellation(msg.sender); + } + if ( + (proposal.initiatedBy == owner && msg.sender != supplier) || + (proposal.initiatedBy == supplier && msg.sender != owner) + ) { + revert NotAuthorizedToAcceptCancellation(msg.sender); + } + + // Finalize the cancellation + delete _cancellationProposals[tokenId]; + _burn(tokenId); + + // Emit the cancellation accepted event + emit CancellationAccepted(tokenId, msg.sender, proposal.refundAmount, proposal.refundCurrency); + } + + /** + * @notice Counters a cancellation proposal with a new proposal. + * + * @param tokenId The token id to counter the cancellation for + * @param newRefundAmount The new proposed refund amount in wei + * @param newRefundCurrency The new ERC20 token address for the refund + */ + function counterCancellationProposal(uint256 tokenId, uint256 newRefundAmount, address newRefundCurrency) external { + address owner = _requireOwned(tokenId); + CancellationProposal storage proposal = _cancellationProposals[tokenId]; + if (!proposal.isActive) { + revert NoActiveCancellationProposal(tokenId); + } + + address supplier = _originalSupplier[tokenId]; + if (msg.sender == proposal.initiatedBy) { + revert NotAuthorizedToCounterCancellation(msg.sender); + } + if ( + (proposal.initiatedBy == owner && msg.sender != supplier) || + (proposal.initiatedBy == supplier && msg.sender != owner) + ) { + revert NotAuthorizedToCounterCancellation(msg.sender); + } + + // Update the proposal with the new values + proposal.refundAmount = newRefundAmount; + proposal.refundCurrency = newRefundCurrency; + + // Emit the countered proposal event + emit CancellationCountered(tokenId, msg.sender, newRefundAmount, newRefundCurrency); + } + + /** + * @notice Cancels an active cancellation proposal. Only the initiator can cancel. + * + * @param tokenId The token id for which to cancel the proposal + */ + function cancelCancellationProposal(uint256 tokenId) external { + CancellationProposal storage proposal = _cancellationProposals[tokenId]; + if (!proposal.isActive) { + revert NoActiveCancellationProposal(tokenId); + } + if (msg.sender != proposal.initiatedBy) { + revert NotAuthorizedToCancelProposal(msg.sender); + } + + // Cancel the proposal + delete _cancellationProposals[tokenId]; + + // Emit the cancellation proposal canceled event + emit CancellationProposalCanceled(tokenId, msg.sender); + } + + /** + * @notice Retrieves the current cancellation proposal status for a given token. + * + * @param tokenId The token id to check the proposal status for + * @return refundAmount The proposed refund amount + * @return refundCurrency The address of the proposed refund currency + * @return initiatedBy The address that initiated the cancellation + * @return isActive The status of the cancellation proposal + */ + function getCancellationProposalStatus( + uint256 tokenId + ) external view returns (uint256 refundAmount, address refundCurrency, address initiatedBy, bool isActive) { + CancellationProposal memory proposal = _cancellationProposals[tokenId]; + return (proposal.refundAmount, proposal.refundCurrency, proposal.initiatedBy, proposal.isActive); + } + + /*************************************************** + * OVERRIDE FUNCTIONS * + ***************************************************/ + + /** + * @notice Checks if the token is transferable, considering reservation and cancellation status. + * + * @param tokenId The token id to check + */ + function checkTransferable(uint256 tokenId) internal override { + super.checkTransferable(tokenId); + if (_cancellationProposals[tokenId].isActive) { + revert TokenHasActiveCancellationProposal(tokenId); + } + } +}