Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ced8cb5
💥 introduced register-unregister wallet in identity
mohamadhammoud Jan 3, 2026
8203ce4
🚨 fix lint
mohamadhammoud Jan 3, 2026
bc8e6bd
Merge branch 'develop' into feature/BT-1447-global-identity-registry-…
mohamadhammoud Jan 3, 2026
b11f323
💥 used require statement
mohamadhammoud Jan 5, 2026
25c74c2
💥 test coverage
mohamadhammoud Jan 5, 2026
a4271e9
💥 resolve require statement
mohamadhammoud Feb 10, 2026
aad1cbc
🚨 fix lint
mohamadhammoud Feb 11, 2026
1a7c009
added nonce per wallet
mohamadhammoud Feb 23, 2026
777b4c9
🚨 fix linter validation
mohamadhammoud Feb 23, 2026
c0dc8fa
🔧 Merge branch 'develop' into feature/BT-1447-global-identity-registr…
mohamadhammoud Feb 24, 2026
c357fb9
♻️ added eip 712 and resolved unlink method
mohamadhammoud Feb 25, 2026
9875667
Merge branch 'develop' into feature/BT-1447-global-identity-registry-…
mohamadhammoud Mar 5, 2026
1dcc2ad
Merge branch 'develop' into feature/BT-1447-global-identity-registry-…
mohamadhammoud Mar 5, 2026
7445cd6
💥 applied nonce for erc712, linked wallet just to the same ID
mohamadhammoud Mar 6, 2026
25faad4
Merge branch 'develop' into feature/BT-1447-global-identity-registry-…
mohamadhammoud Mar 6, 2026
9434b6f
return format section
mohamadhammoud Mar 6, 2026
ba2332f
fix lint
mohamadhammoud Mar 6, 2026
b88aff1
🔀 Merge branch 'develop' into feature/BT-1447-global-identity-registr…
mohamadhammoud Mar 9, 2026
6c71d8a
🔀 Merge branch 'develop' into feature/BT-1447-global-identity-registr…
mohamadhammoud Mar 9, 2026
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
3 changes: 1 addition & 2 deletions .github/scripts/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ echo "$COVERAGE_OUTPUT"
echo "======================="

TOTAL_LINE=$(echo "$COVERAGE_OUTPUT" | grep "| Total.*|")

if [ -z "$TOTAL_LINE" ]; then
echo "❌ Could not find Total coverage line"
exit 1
Expand Down Expand Up @@ -44,7 +43,7 @@ fi

if [ $FAIL = 1 ]; then
echo ""
echo "Coverage check failed! All metrics must be 100%"
echo "Coverage check failed! All coverage metrics must be 100%"
exit 1
else
echo "✅ Coverage requirements met!"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- develop
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -44,4 +45,5 @@ jobs:
run: forge test -vvv

- name: Run coverage
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
run: ./.github/scripts/coverage.sh
5 changes: 4 additions & 1 deletion .husky/commit-msg
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
npx commitlint --edit $1
#!/usr/bin/env sh
. "$(dirname "$0")/_/h"

npx commitlint --edit $1
5 changes: 4 additions & 1 deletion .husky/post-checkout
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
npm i
#!/usr/bin/env sh
. "$(dirname "$0")/_/h"

npm i
5 changes: 4 additions & 1 deletion .husky/post-merge
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
npm i
#!/usr/bin/env sh
. "$(dirname "$0")/_/h"

npm i
5 changes: 3 additions & 2 deletions .husky/pre-commit
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
npm run lint
.github/scripts/coverage.sh
STAGED_FILES=$(git diff --cached --name-only --diff-filter=d)
forge fmt
echo "$STAGED_FILES" | xargs -r git add
21 changes: 8 additions & 13 deletions contracts/ClaimIssuer.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.27;

import { IIdentity, Identity } from "./Identity.sol";
import { IClaimIssuer } from "./interface/IClaimIssuer.sol";
import { Errors } from "./libraries/Errors.sol";
import { KeyPurposes } from "./libraries/KeyPurposes.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IIdentity, Identity} from "./Identity.sol";
import {IClaimIssuer} from "./interface/IClaimIssuer.sol";
import {Errors} from "./libraries/Errors.sol";
import {KeyPurposes} from "./libraries/KeyPurposes.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract ClaimIssuer is IClaimIssuer, Identity, UUPSUpgradeable {

mapping(bytes => bool) public revokedClaims;

/**
* @notice Constructor for direct deployments (non-proxy)
* @param initialManagementKey The initial management key for the ClaimIssuer
*/
// solhint-disable-next-line no-empty-blocks
constructor(address initialManagementKey) Identity(initialManagementKey, false) { }
constructor(address initialManagementKey) Identity(initialManagementKey, false) {}

/**
* @notice External initializer for proxy deployments
Expand Down Expand Up @@ -81,7 +80,7 @@ contract ClaimIssuer is IClaimIssuer, Identity, UUPSUpgradeable {
_identity.addClaim.selector, _topic, _scheme, address(this), _signature, _data, _uri
);

try _identity.execute(address(_identity), 0, addClaimData) { }
try _identity.execute(address(_identity), 0, addClaimData) {}
catch {
revert Errors.CallFailed();
}
Expand Down Expand Up @@ -131,9 +130,6 @@ contract ClaimIssuer is IClaimIssuer, Identity, UUPSUpgradeable {
*/
// solhint-disable-next-line func-name-mixedcase
function __ClaimIssuer_init(address initialManagementKey) internal {
// Initialize UUPS upgradeability
__UUPSUpgradeable_init();

// Initialize Identity functionality
__Identity_init(initialManagementKey);
}
Expand All @@ -148,5 +144,4 @@ contract ClaimIssuer is IClaimIssuer, Identity, UUPSUpgradeable {
// Only management keys can authorize upgrades
// This prevents unauthorized upgrades and potential security issues
}

}
28 changes: 13 additions & 15 deletions contracts/Identity.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.27;

import { KeyManager } from "./KeyManager.sol";
import { IClaimIssuer } from "./interface/IClaimIssuer.sol";
import { IERC734 } from "./interface/IERC734.sol";
import { IERC735 } from "./interface/IERC735.sol";
import { IIdentity } from "./interface/IIdentity.sol";
import { Errors } from "./libraries/Errors.sol";
import { KeyPurposes } from "./libraries/KeyPurposes.sol";
import { Structs } from "./storage/Structs.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {KeyManager} from "./KeyManager.sol";
import {IClaimIssuer} from "./interface/IClaimIssuer.sol";
import {IERC734} from "./interface/IERC734.sol";
import {IERC735} from "./interface/IERC735.sol";
import {IIdentity} from "./interface/IIdentity.sol";
import {Errors} from "./libraries/Errors.sol";
import {KeyPurposes} from "./libraries/KeyPurposes.sol";
import {Structs} from "./storage/Structs.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {MulticallUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
* @title Identity
Expand All @@ -35,7 +35,6 @@ import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableS
* in upgradeable contracts.
*/
contract Identity is Initializable, IIdentity, KeyManager, MulticallUpgradeable {

using EnumerableSet for EnumerableSet.Bytes32Set;

/**
Expand Down Expand Up @@ -317,5 +316,4 @@ contract Identity is Initializable, IIdentity, KeyManager, MulticallUpgradeable
s.slot := slot
}
}

}
20 changes: 9 additions & 11 deletions contracts/IdentityUtilities.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.27;
pragma solidity 0.8.30;

import { IClaimIssuer } from "./interface/IClaimIssuer.sol";
import { IIdentity } from "./interface/IIdentity.sol";
import { IIdentityUtilities } from "./interface/IIdentityUtilities.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {IClaimIssuer} from "./interface/IClaimIssuer.sol";
import {IIdentity} from "./interface/IIdentity.sol";
import {IIdentityUtilities} from "./interface/IIdentityUtilities.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

/**
* @title IdentityUtilities
Expand All @@ -14,7 +14,6 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgrade
* using ABI-encoded `string[]` arrays.
*/
contract IdentityUtilities is IIdentityUtilities, AccessControlUpgradeable, UUPSUpgradeable {

/// @notice Role identifier for accounts allowed to manage topics
bytes32 public constant TOPIC_MANAGER_ROLE = keccak256("TOPIC_MANAGER_ROLE");

Expand Down Expand Up @@ -50,7 +49,7 @@ contract IdentityUtilities is IIdentityUtilities, AccessControlUpgradeable, UUPS
_validateFieldArrays(encodedFieldNames, encodedFieldTypes);

_topics[topicId] =
TopicInfo({ name: name, encodedFieldNames: encodedFieldNames, encodedFieldTypes: encodedFieldTypes });
TopicInfo({name: name, encodedFieldNames: encodedFieldNames, encodedFieldTypes: encodedFieldTypes});

emit TopicAdded(topicId, name, encodedFieldNames, encodedFieldTypes);
}
Expand All @@ -69,7 +68,7 @@ contract IdentityUtilities is IIdentityUtilities, AccessControlUpgradeable, UUPS
_validateFieldArrays(encodedFieldNames, encodedFieldTypes);

_topics[topicId] =
TopicInfo({ name: name, encodedFieldNames: encodedFieldNames, encodedFieldTypes: encodedFieldTypes });
TopicInfo({name: name, encodedFieldNames: encodedFieldNames, encodedFieldTypes: encodedFieldTypes});

emit TopicUpdated(topicId, name, encodedFieldNames, encodedFieldTypes);
}
Expand Down Expand Up @@ -173,7 +172,7 @@ contract IdentityUtilities is IIdentityUtilities, AccessControlUpgradeable, UUPS
* @dev Required override for UUPS upgradability authorization
* @param newImplementation Address of the new implementation
*/
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { }
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}

function _countTotalClaims(address identity, uint256[] calldata topicIds) internal view returns (uint256 total) {
for (uint256 i = 0; i < topicIds.length; i++) {
Expand Down Expand Up @@ -241,5 +240,4 @@ contract IdentityUtilities is IIdentityUtilities, AccessControlUpgradeable, UUPS

/// @dev Reserved storage space to allow future layout changes
uint256[50] private __gap; // solhint-disable-line ordering

}
21 changes: 9 additions & 12 deletions contracts/KeyManager.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.27;

import { IERC734 } from "./interface/IERC734.sol";
import { Errors } from "./libraries/Errors.sol";
import { KeyPurposes } from "./libraries/KeyPurposes.sol";
import { KeyTypes } from "./libraries/KeyTypes.sol";
import { Structs } from "./storage/Structs.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IERC734} from "./interface/IERC734.sol";
import {Errors} from "./libraries/Errors.sol";
import {KeyPurposes} from "./libraries/KeyPurposes.sol";
import {KeyTypes} from "./libraries/KeyTypes.sol";
import {Structs} from "./storage/Structs.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
* @title KeyManager
Expand All @@ -24,7 +24,6 @@ import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableS
* in upgradeable contracts.
*/
contract KeyManager is IERC734 {

using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.Bytes32Set;

Expand Down Expand Up @@ -54,9 +53,8 @@ contract KeyManager is IERC734 {
* Formula: keccak256(abi.encode(uint256(keccak256(bytes(id))) - 1)) & ~bytes32(uint256(0xff))
* where id is the namespace identifier
*/
bytes32 internal constant _KEY_STORAGE_SLOT = keccak256(
abi.encode(uint256(keccak256(bytes("onchainid.keymanager.storage"))) - 1)
) & ~bytes32(uint256(0xff));
bytes32 internal constant _KEY_STORAGE_SLOT =
keccak256(abi.encode(uint256(keccak256(bytes("onchainid.keymanager.storage"))) - 1)) & ~bytes32(uint256(0xff));

/**
* @notice Prevent any direct calls to the implementation contract (marked by _canInteract = false).
Expand Down Expand Up @@ -312,7 +310,7 @@ contract KeyManager is IERC734 {
ks.executions[_id].approved = true;

// solhint-disable-next-line avoid-low-level-calls
(success,) = ks.executions[_id].to.call{ value: (ks.executions[_id].value) }(ks.executions[_id].data);
(success,) = ks.executions[_id].to.call{value: (ks.executions[_id].value)}(ks.executions[_id].data);

if (success) {
emit Executed(_id, ks.executions[_id].to, ks.executions[_id].value, ks.executions[_id].data);
Expand Down Expand Up @@ -394,5 +392,4 @@ contract KeyManager is IERC734 {
s.slot := slot
}
}

}
10 changes: 4 additions & 6 deletions contracts/factory/ClaimIssuerFactory.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.27;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { CREATE3 } from "solady/src/utils/CREATE3.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {CREATE3} from "solady/src/utils/CREATE3.sol";

import { Errors } from "../libraries/Errors.sol";
import {Errors} from "../libraries/Errors.sol";

contract ClaimIssuerFactory is Ownable {

address private _implementation;
mapping(address => address) private _deployedClaimIssuers;
mapping(address => bool) private _blacklistedAddresses;
Expand Down Expand Up @@ -114,5 +113,4 @@ contract ClaimIssuerFactory is Ownable {

return claimIssuerAddress;
}

}
40 changes: 35 additions & 5 deletions contracts/factory/IIdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.27;

interface IIdFactory {

/// events
// event emitted whenever a single contract is deployed by the factory
event Deployed(address indexed _addr);
Expand Down Expand Up @@ -69,9 +68,10 @@ interface IIdFactory {
/**
* @dev function used to link a new wallet to an existing identity
* @param _newWallet the address of the wallet to link
* requires msg.sender to be linked to an existing onchainid
* requires msg.sender to be actively linked to an existing onchainid
* the _newWallet will be linked to the same OID contract as msg.sender
* _newWallet cannot be linked to an OID yet
* _newWallet cannot be actively linked to an OID yet
* if _newWallet was previously unlinked, it can only be re-linked to the same identity
* _newWallet cannot be address 0
* cannot link more than 100 wallets to an OID, for gas consumption reason
*/
Expand All @@ -80,12 +80,36 @@ interface IIdFactory {
/**
* @dev function used to unlink a wallet from an existing identity
* @param _oldWallet the address of the wallet to unlink
* requires msg.sender to be linked to the same onchainid as _oldWallet
* requires msg.sender to be actively linked to the same onchainid as _oldWallet
* msg.sender cannot be _oldWallet to keep at least 1 wallet linked to any OID
* _oldWallet cannot be address 0
* unlinked wallets remain bound to their identity and can only be re-linked to the same identity
*/
function unlinkWallet(address _oldWallet) external;

/**
* @dev function used to link a wallet to an identity using signature verification
* @param wallet the address of the wallet to link
* @param signature EIP-712 signature provided by the wallet
* @param nonce the current nonce of the wallet (prevents replay attacks)
* @param expiry expiry timestamp for the signature
* requires the wallet to sign an EIP-712 typed message binding wallet, identity, nonce, and expiry
* requires msg.sender to be the identity contract (called via execute())
* if the wallet was previously unlinked, it can only be re-linked to the same identity
* wallet cannot be address 0
* signature must not be expired
* nonce must match the current nonce of the wallet
*/
function linkWalletWithSignature(address wallet, bytes calldata signature, uint256 nonce, uint256 expiry) external;

/**
* @dev function used to unlink a wallet from an identity, callable by the identity contract
* @param wallet the address of the wallet to unlink
* requires msg.sender to be the identity contract that the wallet is linked to
* wallet cannot be address 0
*/
function unlinkWalletByIdentity(address wallet) external;

/**
* @dev function used to register an address as a token factory
* @param _factory the address of the token factory
Expand Down Expand Up @@ -131,6 +155,13 @@ interface IIdFactory {
*/
function isTokenFactory(address _factory) external view returns (bool);

/**
* @dev getter for the current nonce of a wallet address (used for EIP-712 replay protection)
* @param owner the wallet address to check the nonce for
* @return the current nonce value
*/
function nonces(address owner) external view returns (uint256);

/**
* @dev getter to know if a salt is taken for the create2 deployment
* @param _salt the salt used for deployment
Expand All @@ -141,5 +172,4 @@ interface IIdFactory {
* @dev getter for the implementation authority used by this factory.
*/
function implementationAuthority() external view returns (address);

}
Loading