Any source code provided on this site is unaudited and provides no guarantee that it will be functional or safe to use. Ensure you test before deploying to mainnet or production use.
Applying Signata Identities to Authentication Systems
Claims-based Enforcement
TODO
Applying Signata Identities to Smart Contracts
An identity by itself is of no value. Its value is derived from those that recognise it, and what systems it can be interconnected to.
This document details the application of Signata identities to blockchain-based systems. It does not prescribe a recommended or best-practice approach for integration, only a starting point that can be developed upon for real-world systems.
Integrating identity validation into smart contracts is extremely simple - connecting to an instance of the SignataIdentity and SignataRights contracts within a network will provide the basis of enforcing Signata-based identity enforcement into any contract function that you wish to use it in.
Simple Access Control
At a basic level, smart contracts can verify that an address has been registered with an identity contract.
For open identity contracts this provides next to no assurance as any address may register themselves as an identity and pass this check. But for an access-controlled identity contract, the provisioning of the identity can be restricted to specific accounts with a process of issuance having been performed by the contract owner.
SignataIdentity private signataIdentity;/* ... */// get delegate - will throw if the identity does not existsignataIdentity.getDelegate(msg.sender);// or check if locked - also will throw if the identity doesn't exist// and will also reject locked identitiesrequire(!signataIdentity.isLocked(msg.sender),"The sender's identity is locked.");
Rights Based Access Control
To enforce stronger access control in contracts, validating that an address holds a specific ERC721 right is possible by checking against those that have been
At a basic level, smart contracts can verify that an address has been registered with an identity contract.
For open identity contracts this provides next to no assurance as any address may register themselves as an identity and pass this check. But for an access-controlled identity contract, the provisioning of the identity can be restricted to specific accounts with a process of issuance having been performed by the contract owner.
SignataRight private signataRight;SignataIdentity private signataIdentity;addressprivate trustedMinter;/* ... */// check the minter of an asserted rightrequire (signataRight.minterOf(nftRightIndex) == trustedMinter,"Right is not from trusted minter");// get the delegate address of the senderaddress delegateAddress = signataIdentity.getDelegate(msg.sender);// check the owner of the asserted rightrequire (signataRight.ownerOf(nftRightIndex) == delegateAddress,"Right is not owned by sender!");
With these three checks above, we've validated that the asserted right of the sender is actually owned by them via their identity delegate key, we've validated that the identity is actually currently active (getDelegate will fail if the identity is currently locked or destroyed), and we've validated that the minting of the right came from a trusted source.
Business Register
Most jurisdictions will manage a public registry of all operating businesses, tracking each with a unique identifier so they're identifiable separately to the individuals that own, operate, or work in those businesses.
TODO
Staking Pool
For staking pools, you can simply deny entry to the pool if the user does not hold a specific identity or right.
And for unstaking, adding the same checks can ensure that a mutated identity cannot claim the rewards if that is a desired outcome.
SignataIdentity private signataIdentity;/* ... */functionstakeTokens(uint256_amount,uint256[] memory_tokenIds) public {require(getLastStakableBlock() > block.number,'this farm is expired and no more stakers can be added' );// try to get the delegate of the account. this will fail if the account isn't registered, or is in an unusable state signataIdentity.getDelegate(msg.sender);_updatePool();if (balanceOf(msg.sender) >0) {_harvestTokens(msg.sender); }uint256 _finalAmountTransferred;if (pool.isStakedNft) {require( _tokenIds.length >0,"you need to provide NFT token IDs you're staking" );for (uint256 _i =0; _i < _tokenIds.length; _i++) { _stakedERC721.transferFrom(msg.sender,address(this), _tokenIds[_i]); } _finalAmountTransferred = _tokenIds.length; } else {uint256 _contractBalanceBefore = _stakedERC20.balanceOf(address(this)); _stakedERC20.transferFrom(msg.sender,address(this), _amount);// in the event a token contract on transfer taxes, burns, etc. tokens// the contract might not get the entire amount that the user originally// transferred. Need to calculate from the previous contract balance// so we know how many were actually transferred. _finalAmountTransferred = _stakedERC20.balanceOf(address(this)).sub( _contractBalanceBefore ); }if (totalSupply() ==0) { pool.creationBlock = block.number; pool.lastRewardBlock = block.number; }_mint(msg.sender, _finalAmountTransferred); StakerInfo storage _staker = stakers[msg.sender]; _staker.amountStaked = _staker.amountStaked.add(_finalAmountTransferred); _staker.blockOriginallyStaked = block.number; _staker.timeOriginallyStaked = block.timestamp; _staker.blockLastHarvested = block.number; _staker.rewardDebt = _staker.amountStaked.mul(pool.accERC20PerShare).div(1e36 );for (uint256 _i =0; _i < _tokenIds.length; _i++) { _staker.nftTokenIds.push(_tokenIds[_i]); }_updNumStaked(_finalAmountTransferred,'add');emitDeposit(msg.sender, _finalAmountTransferred);}/* ... */
Token Lock
Token locks can be restricted to only accept locks from validated identities, before the lock is created.
SignataIdentity private signataIdentity;/* ... */functioncreateLocker(address_tokenAddress,uint256_amountOrTokenId,uint48_end,uint8_numberVests,address[] memory_withdrawableAddresses,bool_isNft) externalpayable {require( _end > block.timestamp,'Locker end date must be after current time.' );// try to get the delegate of the account. this will fail if the account isn't registered, or is in an unusable state signataIdentity.getDelegate(msg.sender);_payForService(0);if (_isNft) { IERC721 _token =IERC721(_tokenAddress); _token.transferFrom(msg.sender,address(this), _amountOrTokenId); } else { IERC20 _token =IERC20(_tokenAddress); _token.transferFrom(msg.sender,address(this), _amountOrTokenId); } lockers.push(Locker({ owner: msg.sender, isNft: _isNft, token: _tokenAddress, amountSupply: _isNft ?1: _amountOrTokenId, tokenId: _isNft ? _amountOrTokenId :0, start:uint48(block.timestamp), end: _end, withdrawable: _withdrawableAddresses, amountWithdrawn:0, numberVests: _isNft ?1: (_numberVests ==0?1: _numberVests) }) );uint16 _newIdx =uint16(lockers.length -1); lockersByOwner[msg.sender].push(_newIdx); lockersByToken[_tokenAddress].push(_newIdx);if (_withdrawableAddresses.length >0) {for (uint16 _i =0; _i < _withdrawableAddresses.length; _i++) { lockersByWithdrawable[_withdrawableAddresses[_i]].push(_newIdx); } }emitCreateLocker(msg.sender, _newIdx);}/* ... */
DAO Governor with Identity Validation
The simplest way to enforce identity usage within a DAO is to just validate the voter when they attempt to cast votes or delegate their voting rights.
Note: this is not a common way to do airdrops, as batch ERC20 token transactions are expensive for the sender. If you do wish to distribute tokens in this manner, then you can skip any recipients that aren't registered identities.
SignataIdentity private signataIdentity;/* ... */functionbulkSendErc20Tokens(address_tokenAddress,Receiver[] memory_addressesAndAmounts) externalpayablereturns (bool) {_payForService(0); IERC20 _token =IERC20(_tokenAddress);for (uint256 _i =0; _i < _addressesAndAmounts.length; _i++) { Receiver memory _user = _addressesAndAmounts[_i];try signataIdentity.getDelegate(_user.userAddress) { _token.transferFrom(msg.sender, _user.userAddress, _user.amountOrTokenId); } catch { } // ignoring the index if the identity was not found }returntrue;}/* ... */
Merkle Airdrop
Merkle-based drops are far more cost effective for distributing tokens to a large number of holders. If you wish to only distribute tokens to created identities, or identities that hold a specific NFT right (such as a KYC claim), then you can simply inject a check into the claim function to prevent invalid identities from claiming the airdrop.
For a merkle-based drop it would be advisable to only create the proofs for addresses that are actually valid in the first place, performing that computation off-chain, but as identities may mutate after the claim contract is deployed it can provide an extra level of on-chain assurance.
/* ... */functionclaim(uint256 index,address account,uint256 amount,bytes32[] calldata merkleProof) externaloverride {require(claimsEnabled,"Airdrop::claim: Claims not enabled.");require(!isClaimed(index),"Airdrop::claim: Drop already claimed.");// try to get the delegate of the account. this will fail if the account isn't registered, or is in an unusable state signataIdentity.getDelegate(account);bytes32 node =keccak256(abi.encodePacked(index, account, amount));require(MerkleProof.verify(merkleProof, merkleRoot, node),"Airdrop::claim: Invalid proof.");_setClaimed(index);require(token.transfer(msg.sender, amount),"Airdrop::claim: Transfer failed.");emitClaimed(index, msg.sender, amount);}/* ... */
Dividend Rewards Distribution
Some DeFi projects use "dividend" based distribution of assets. Enforcing of holding an identity, or a specific NFT right, can be simply included in-line with functions that set the balance of the address. If it holds a Signata Identity, then it can receive a balance fo the dividend contract. Otherwise it is treated like an excluded contract
/* ... */functionsetBalance(address payable account,uint256 newBalance)externalonlyOwner{if (excludedFromRewards[account]) {return; }// if it's not a registered identity, then it will be treated like it's in the exclusion listtry signataIdentity.getDelegate(account) {if (newBalance >= minTokenBalanceForRewards) {_setBalance(account, newBalance); } else {_setBalance(account,0); } } catch { return; }}/* ... */