As of 3 Nov 2026, The Unseen has 1 audit report on Trustblock, by CertiK, dated 5 Aug 2024. That report lists 1 open critical finding.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Unseen - Audit | Security firm: CertiK | Date: | Scope: Files named in the report | 1 open critical finding | Original removed |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 1 | 0 | 1 |
| High | 2 | 0 | 1 |
| Medium | 0 | 0 | 0 |
| Low | 2 | 0 | 14 |
The `ProxyRegistry` contract plays an important role in the `TheGenerates` contract. Specifically, in the `MarketsPreapproved` contract (which is inherited by `TheGenerates` contract), the `isApprovedForAll()` function returns `true` if the `operator` is a proxy for the user. Subsequently, the `isAuthorized` modifier defined in the `ERC4907A` contract would pass for the user's `tokenId`, which means that the proxy can control the NFT corresponding to the `tokenId`. The proxy is recorded in the `ProxyRegistry` contract, specifically the `proxies` mapping. The intended behavior is that a user can only set its own proxy, and not any other user's proxy, so a user can control the NFT either directly or via its proxy. However, the `transferAccessTo()` function in the `ProxyRegistry` contract is capable of updating the `proxies[to]` mapping, and the access control in the `ProxyRegistry` and `AuthenticatedProxy` contracts is insufficient. This enables user Bob to change the `proxies[Alice]` to his own proxy, and subsequently takes control of any `tokenId` that Alice owns via Bob's proxy. Note that in the `AuthenticatedProxy` contract, the `transferProxyOwnership()` function enables the proxy owner to transfer the ownership to a new owner, and update the mapping in the registry. However, the `AuthenticatedProxy` contract also contains a `proxy()` function and a `proxyAssert()` function that can also call the `transferAccessTo()` function of the Registry, but does NOT change the `owner` variable. This allows Bob to retain ownership of his proxy, while updating the `proxies[Alice]` value in the `ProxyRegistry` contract. A proof of concept test case is added to the `exchange.registry.spec.ts` file to demonstrate the above. This vulnerability impacts all other contracts that utilize the registry and proxy contracts, including `TheGenerates` and `UnseenExchange`. For `TheGenerates`, a malicious user could effectively take control of another user's NFT. For `UnseenExchange`, the ability to manipulate the `proxy` address in line 356 of the `ExchangeCore` contract allows a malicious user to brick legitimate atomic match transactions.
Open the reportThe `init` function is supposed to be called by the contract owner only one time, as the comment in line 116 indicates. However, the check in line 120 does not prevent the function from being called more than once. As a result, the owner can change the `tokenId` and add new minters at will. This represents a significant risk to the token holders, as a new minter can mint a large amount of tokens and also arbitrarily burn tokens from other addresses. ```solidity=113 /** * @notice Initialize the bridge parmas. * * This can only be called by the contract owner and one time. */ function init(bytes32 tokenId_, address tokenManager) external onlyOwner { if (tokenId_ == bytes32(0)) revert TokenIdZero(); if (tokenId_ == tokenId) revert TokenIdIsAlreadySet(); tokenId = tokenId_; _addMinter(tokenManager); } ```
Open the reportThe `validateOrderAuthorization()` function is intended to "validate if maker signed the order hash" (comment in line 173). However, in line 459 and 471, the `executor` address is passed in to the function instead. If the `maker` and `executor` are different addresses, this represents the inconsistency between the intended design and the actual behavior of the code.
Open the reportAccording to EIP 4906 (https://eips.ethereum.org/EIPS/eip-4906), The `supportsInterface` method MUST return `true` when called with `0x49064906`. The `VestingLockup` contract inherits `IERC4906` but the `supportsInterface` method is not updated to conform to the EIP 4906 standard.
Open the reportWhen the owner creates a new schedule either via `createSchedule()` or `createMultiSchedules()`, there is no check that the `params.recipient` can handle the received ERC721. If `params.recipient` is a smart contract that cannot handle the ERC721, the corresponding vesting schedule could be unwithdrawable.
Open the reportFunctions with names starting with `_` should be declared as `private`/`internal`.
Open the reportThe `ecrecover()` function can return the zero address (0x0000000000000000000000000000000000000000) if the signature was invalid or the message was malformed. When the smart contract does not check the output of `ecrecover()` and assumes it is always a valid address, vulnerability may arise.
Open the reportIt is important to emit events for sensitive actions, particularly those that can be executed by centralized roles or administrators. This ensures transparency and enables tracking of critical changes, which is essential for security and trust in the system. Missing event logs can indeed result in a lack of visibility and potential information loss.
Open the reportThe **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.
Open the reportThe function `setDefaultRoyalty` receives the `feeNumerator` is used for royalty payment. In the marketplaces which support EIP-2981, `salePrice * ( feeNumerator/10000 `) will be sent to the royalty recipient. But there's no upper limit for `feeNumerator` when setting the default royalty and it can be as high as 100%.
Open the reportIn the library `ArrayUtils`, there is a function `guardedArrayReplace()` that is used to replace bytes in an array with bytes in another array, guarded by a bitmask. This function should process the array in two steps: quotient and remainder. The local variable `words` is used to separate the two steps, and it is calculated by `array.length / 0x20`. The first quotient step works well following this calculation. However, the later remainder case also uses this value to determine if there is a set of leftover bytes by checking `words != 0`. Since `words` holds the quotient, the remainder is not properly handled, causing issues when the function processes the `words != 0` condition. This bug can lead to arbitrary storage writes. If `array.length` is exactly divisible by `0x20`, the copying happens correctly within the loop. In other cases, the function enters the flawed logic and attempts to copy an extra word beyond the array bounds, leading to out-of-bounds access. This incorrect logic can be exploited to overwrite memory at the end of the target array, potentially affecting any unknown memory area.
Open the reportThe `_checkActive()` function would revert if `startTime == endTime`, which indicates that the `startTime` and `endTime` of any mint (Public, AllowList, or Signed) cannot be the same. However, in the `updatePublicDrop()` function, the check in line 568 - 573 allows `publicDrop.startTime == publicDrop.endTime`, which is inconsistent with the minting logic. This means that it is possible to set public drop parameters that is guaranteed to fail when users attempt to mint.
Open the reportWhen a contract function accepts native tokens and executes a `.call()` or similar function that also forwards native tokens, it's important to check for and refund any remaining balance. This is because some of the supplied value may not be used during the call execution due to gas constraints, a revert in the called contract, or simply because not all the value was needed. If this remaining balance is not refunded, it will be locked in the contract.
Open the reportThere are several typos in the contracts, please see the locations above, where the words with typos are listed: - targer - implemenation
Open the reportThe `.send()` function intends to transfer an ETH amount with a fixed amount of 2300 gas. This function is not equipped to handle changes in the underlying `.send()` and `.transfer()` functions which may supply different amounts of gas in the future. Additionally, if the recipient implements a fallback function containing some sort of logic, this may inevitably revert, meaning the vault and owner of the contract will never be able to call certain sensitive functions. Reference: [Solidity: Sending Ether (transfer, send, call)](https://solidity-by-example.org/sending-ether/)
Open the reportThe linked variables could be declared as constant since these state variables are never modified. Compared to regular state variables, the gas costs of constant variables are much lower.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="unseen"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us