Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
25 changes: 25 additions & 0 deletions contracts/factory/IIdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ interface IIdFactory {
*/
function unlinkWallet(address _oldWallet) external;

/**
* @dev function used to register a wallet to an identity using signature verification
* @param wallet the address of the wallet to register
* @param signature signature provided by the wallet
* @param expiry expiry timestamp for the signature
* requires the wallet to sign a message binding wallet, identity, expiry, contract and chain id
* requires the wallet to hold a MANAGEMENT key on the identity
* requires msg.sender to be the identity contract
* wallet cannot be address 0
* signature must not be expired
*/
function registerWalletToIdentity(
address wallet,
bytes calldata signature,
uint256 expiry
) external;

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

/**
* @dev function used to register an address as a token factory
* @param _factory the address of the token factory
Expand Down
108 changes: 108 additions & 0 deletions contracts/factory/IdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
pragma solidity ^0.8.27;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import { IdentityProxy } from "../proxy/IdentityProxy.sol";
import { IIdFactory } from "./IIdFactory.sol";
import { IERC734 } from "../interface/IERC734.sol";
import { IIdentity } from "../interface/IIdentity.sol";
import { Errors } from "../libraries/Errors.sol";
import { KeyPurposes } from "../libraries/KeyPurposes.sol";
import { KeyTypes } from "../libraries/KeyTypes.sol";

contract IdFactory is IIdFactory, Ownable {
using ECDSA for bytes32;

// address of the _implementationAuthority contract making the link to the implementation contract
address public immutable implementationAuthority;

Expand Down Expand Up @@ -220,6 +224,86 @@ contract IdFactory is IIdFactory, Ownable {
emit WalletUnlinked(_oldWallet, _identity);
}

/**
* @dev See {IdFactory-registerWalletToIdentity}.
*/
function registerWalletToIdentity(
address wallet,
bytes calldata signature,
uint256 expiry
) external override {
if (wallet == address(0)) {
revert Errors.ZeroAddress();
}
if (block.timestamp > expiry) {
revert Errors.SignatureExpired(expiry);
}

address identity = msg.sender;
bytes32 structHash = keccak256(
abi.encode(wallet, identity, expiry, address(this), block.chainid)
);

address signer = _recoverWalletSigner(structHash, signature);
if (signer != wallet) {
revert Errors.InvalidSignature();
}

// require the wallet is a MANAGEMENT key on the identity
bytes32 key = keccak256(abi.encode(wallet));
bool hasManagement = IIdentity(identity).keyHasPurpose(
key,
KeyPurposes.MANAGEMENT
);
if (!hasManagement) {
revert Errors.MissingManagementKey();
}

// Check if wallet is already linked
require(
_userIdentity[wallet] == address(0),
Errors.WalletAlreadyLinkedToIdentity(wallet)
);
require(
_tokenIdentity[wallet] == address(0),
Errors.TokenAlreadyLinked(wallet)
);

// Check max wallets per identity
require(
_wallets[identity].length < 101,
Errors.MaxWalletsPerIdentityExceeded()
);

_userIdentity[wallet] = identity;
_wallets[identity].push(wallet);
emit WalletLinked(wallet, identity);
}

/**
* @dev See {IdFactory-unregisterWalletFromIdentity}.
*/
function unregisterWalletFromIdentity(address wallet) external override {
if (wallet == address(0)) {
revert Errors.ZeroAddress();
}
if (_userIdentity[wallet] != msg.sender) {
revert Errors.WalletNotLinked();
}

address identity = _userIdentity[wallet];
delete _userIdentity[wallet];
uint256 length = _wallets[identity].length;
for (uint256 i = 0; i < length; i++) {
if (_wallets[identity][i] == wallet) {
_wallets[identity][i] = _wallets[identity][length - 1];
_wallets[identity].pop();
break;
}
}
emit WalletUnlinked(wallet, identity);
}

/**
* @dev See {IdFactory-getIdentity}.
*/
Expand Down Expand Up @@ -269,6 +353,30 @@ contract IdFactory is IIdFactory, Ownable {
return _tokenFactories[_factory];
}

/**
* @dev Recovers the wallet signer from a structHash using the eth_sign prefix.
* @param structHash hashed payload binding wallet, identity, expiry, contract and chain id.
* @param signature signature provided by the wallet.
* @return signer recovered address or address(0) on recover error.
*/
function _recoverWalletSigner(
bytes32 structHash,
bytes calldata signature
) internal pure returns (address) {
bytes32 digest = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", structHash)
);

(address signer, ECDSA.RecoverError error, ) = ECDSA.tryRecover(
digest,
signature
);
if (error != ECDSA.RecoverError.NoError) {
return address(0);
}
return signer;
}

// deploy function with create2 opcode call
// returns the address of the contract created
function _deploy(
Expand Down
12 changes: 12 additions & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ library Errors {
/// @notice Reverts if the factory is already registered
error AlreadyAFactory(address factory);

/// @notice Reverts when the recovered signer does not match the wallet being registered
error InvalidSignature();

/// @notice Reverts when the provided signature has expired
error SignatureExpired(uint256 expiry);

/// @notice Reverts when the wallet does not hold a management key on the identity
error MissingManagementKey();

/// @notice Reverts when the wallet is not linked to msg.sender during removal
error WalletNotLinked();

/// @notice Reverts if the function is called on the sender address
error CannotBeCalledOnSenderAddress();

Expand Down
Loading
Loading