Proof Of Attendance Verified

Decentralized protocol that allows the organizers of social activities to issue verifiable statements that certify attendance.

Search POAV

Features

The protocol is 100% decentralized and has been designed to facilitate the process of certification and verification of a claim.

Affirmations issued to those attending an educational event or social activity

The organizer creates a POAV and can issue the proof of attendance to the wallet of each participant.

  • The organizer creates a badge and assigns it a reputation value.
  • The POAV issuer creates the metadata associated with the badge. Both files must be available on a web server or an IPFS.
  • The POAV can be minted directly by the issuer or authorized by an operator to do it for them.
  • Signature endorsement allows the network gas rate to be paid by a third party.
  • POAVs can be claimed by the attendee himself or submitted by a third party.

The issuer sends each attendee a POAV as proof of attendance at a social activity, being able to revoke it at any time.

Open validation

The authenticity of a claim can be verified without the need for intermediaries

Open custody

It uses blockchain and IPFS networks for proof of integrity, ownership, authorship and timestamp

Open Badges

Organizers can create their own commemorative badges for their events and associate metadata

Signed affirmations

The organizers can sign each declaration or endorse a third party that performs batch issues

Non-transferable Affirmations

The subjects receiving the NFT-based affirmations cannot transfer it to another wallet

Digital identity

Attendees can receive these affirmations at an address compatible with ECDSA-based wallets

Open validation

The open and decentralized architecture of POAV allows each statement to be verified by third parties, without the need for intermediaries.

The POAVs have states, and can only be modified by the issuer:


enum Status {Undefined, Active, Inactive, Revoked, StandBy}
event POAVStatusChanged(string indexed POAV, address indexed subject, Status _status);

function changeStatus(string memory _POAV, address _subject, Status _status) public returns (bool) {
    require(issuersOfPOAV[_POAV]==msg.sender, "Does not have access");
    require(statusOfPOAVSubject[_POAV][_subject] != _status, "There is no change of state");
    statusOfPOAVSubject[_POAV][_subject] = _status;
    emit POAVStatusChanged(_POAV, _subject, _status);
    return true;
} 

Verifiers can validate the status of a POAV without the need to consult the issuer:


enum Status {Undefined, Active, Inactive, Revoked, StandBy}
function getStatusOf(string calldata _POAV, address _subject) external view returns (Status) {
    return (statusOfPOAVSubject[_POAV][_subject]);
} 

Open custody

The open architecture of POAV proposes that the sender records the metadata of a social activity on IPFS servers; then the CID must be registered in the contracts deployed in different blockchain networks, facilitating the verification of the attendees, integrity of the statement and the authorship of the issuer.

  • IPFS: Custody of the metadata or badge of the event or social activity.
  • Blockchain: Records the issuer's assertion associated with the attendee's identity.

IPFS - Metadata

It is expected to be created in JSON format with at least the following attributes:


{
  "name": "Evento de Prueba",
  "image": "ipfs://QmXAwfftb46rC1gBzVLMoPD3wpddcB3mu9sVNe5q2cN9vE",
  "description": "Probando el evento",
  "attributes": [{"value": "2022-08-23 01:26:30","trait_type": "date"},...],
  "tags": ["POAV",...]
} 

NFT - Blockchain

The issuer of the POAV must register (mint) the CID of the event or social activity in the contract:


function mint(string memory _POAV) public returns (bool) {
    _mintPOAV( _POAV, msg.sender, block.timestamp);
    return true;
}

function _mintPOAV(string memory _POAV, address _issuer, uint256 _validFrom) private returns (bool) {
    require(issuersOfPOAV[_POAV] == address(0), "POAV already exists");
    issuersOfPOAV[_POAV] = _issuer;
    validFromOfPOAV[_POAV] = _validFrom;
    _POAVs++;
    emit POAVMinted(_issuer, _POAV, _validFrom);
    return true;
}

Frequently Asked Questions

In this section you will find answers to frequently asked questions about the POAV protocol, which we have called: 0xPOAV. We hope that this space will help you to understand even better the operation and the methodology for its implementation.

  • How to create a DAO?

    To create a DAO, a unique identifier must be registered, which can be a CID from metadata in IPFS, a website URI, a domain name, or any text you choose.

    Rules

    • That the name of a DAO cannot be repeated and cannot be changed.
    • The sender of the transaction becomes the owner of the DAO, can transfer ownership or add to other wallets to create POAVs associated with that DAO.

    Interface (DAOToken)

    
    function createDAO(string memory DAOURI) external returns (bool);
    function transferOwnerDAO(string memory DAOURI, address newOwner) external returns (bool)
    function addMinter(string memory DAOURI, address operator) public returns (bool);
    function deleteMinter(string memory DAOURI, address operator) public returns (bool);
    function getIssuerDAOOf(string memory DAOURI) external view returns (address);
    function getValidFromDAOOf(string memory DAOURI) external view returns (uint256);
    function getPOAVListFromDAO(string memory DAOURI) external view returns (string[] memory);
    function getOperatorsListFromDAO(string memory DAOURI) public view returns(address[] memory operators);
    function isOperatorOfDAO(string memory DAOURI, address _address) public view returns(bool);
    function isPOAVOfDAO(string memory DAOURI, string memory POAVURI) public view returns(bool);
    function getDAOOf(string memory POAVURI) external view returns (string memory);
    function totalDAOs() external view returns (uint256);
    function totalMinters(string memory DAOURI) external view returns (uint256);
    function totalDAOOf (address subject) external view returns(uint256);
    
  • This hash is used for the delegation of signatures from one wallet to another. It is used to prevent the POAV owner from having to pay for gas, delegating that responsibility to another wallet. There are 2 domains that allow to generate the EIP712 Hash:

    • EIP712DOMAIN_TYPEHASH.
    • VERIFIABLE_CREDENTIAL_TYPEHASH.

    You can find in the documentation the way of calculation; however, to facilitate the calculation process, the following APIs have been created that allow: Calculate it and verify the signature.

    Rules

    • The EIP712Domain is linked to the contract and the blockchain network used, preventing the operator from sending it to an improper network.

    Contract (EIP712)

    
    contract EIP712  {
      using ECDSA for bytes32;
      bytes32 DOMAIN_SEPARATOR;
      bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
      bytes32 constant internal VERIFIABLE_CREDENTIAL_TYPEHASH = keccak256("VerifiableCredential(address issuer,address subject,bytes32 POAV,uint256 validFrom,uint256 validTo)");
      struct EIP712Domain {string name;string version;uint256 chainId;address verifyingContract;}
      constructor()  {
            DOMAIN_SEPARATOR = hashEIP712Domain(
                EIP712Domain({
                    name : "EIP712Domain",
                    version : "1",
                    chainId : 100,
                    verifyingContract : address(this)
                    }));
      }
      function hashEIP712Domain(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
            return keccak256(abi.encode(EIP712DOMAIN_TYPEHASH,keccak256(bytes(eip712Domain.name)),keccak256(bytes(eip712Domain.version)),eip712Domain.chainId,eip712Domain.verifyingContract));
        }
    
        function hashVerifiableCredential(address _issuer,address _subject,string memory POAVURI,uint256 _validFrom,uint256 _validTo) internal pure returns (bytes32) {//0xAABBCC11223344....556677
            return keccak256(abi.encode(VERIFIABLE_CREDENTIAL_TYPEHASH,_issuer,_subject,POAVURI,_validFrom,_validTo));
        }
    
        function _hashForSigned(string memory _credentialSubject, address _issuer, address _subject, uint validFrom, uint validTo) internal view returns (bytes32) {
            bytes32 digest = keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR,
                    hashVerifiableCredential(_issuer, _subject, _credentialSubject, validFrom, validTo)
                )
            );
            return (digest);
        }
        
        function _validateSignature(string memory _credentialSubject, address _issuer, address _subject, uint validFrom, uint validTo,bytes32  _credentialHash, bytes memory _signature ) internal view 
            returns (address issuer, bytes32 hashSha256, bytes32 hashKeccak256) {
            return (_credentialHash.recover(_signature), _hashForSigned(_credentialSubject, _issuer, _subject, validFrom, validTo), 
            keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hashForSigned(_credentialSubject, _issuer, _subject, validFrom, validTo))));
        }
    }
    
  • The reputation of each member of a DAO is earned by participating in the events or social activities that are promoted by the DAO. When an attendee earns a POAV, she receives a number of tokens called FUNToken.

    The operators of each DAO when they create a POAV, assign it the amount of reputation that each attendee of the event will earn.

    Rules

    • Reputation is not transferable.
    • Assigned when a POAV is received.
    • A POAV can assign reputation to attendees when they are created by a DAO.

    Contract (EIP712)

    
    contract FANToken {
      //DAO, address ==> Balance
      mapping(string => mapping(address => uint256)) internal _balanceFANOf;
      //POAV ==> claims
      mapping(string => uint256) internal _claimFANToken;
    
      function balanceFANOf (string memory DAOURI, address subject) external view returns(uint256) {
        return (_balanceFANOf[DAOURI][subject]);
      }
    
      function claimFANTokenOf (string memory POAVURI) external view returns(uint256) {
        return (_claimFANToken[POAVURI]);
      }
      
      function mintFromDAO(string memory DAOURI, string memory POAVURI, uint256 _FanToken);
      
      function _claim(string memory POAVURI, address _subject) private returns (uint256) {
             ...
            _balanceFANOf [DAOOf[POAVURI]][_subject] = _claimFANToken[POAVURI];
            ...
        }
    }
    
  • In the case of minting a POAV within a DAO, the wallet is required to have the role of operator. This role is assigned by the creator of the DAO, which can be revoked at any time.

    Consider that a DAO can escrow that wallet in a new contract that authorizes those approvals or revocations based on community votes.

    In the case of minting a POAV without associating it to a DAO, it can be created by any wallet, no authorization is required.

    Rules

    • The POAV must be unique.
    • The creator of the POAV must accredit the attendance of the participants.
    • The creator of the POAV can change the status (Revoke it, delete it or reinstate it).
    • The creator of the POAV can burn or closed it and from that moment on, new attendance accreditations can no longer be issued to the participants.

    Contract (POAVToken)

    
    contract POAVToken {
        ...
        function mintFromDAO(string memory DAOURI, string memory POAVURI, uint256 _FanToken) public returns (bool) {
           _validateDAO(DAOURI, POAVURI, msg.sender);
           _mintPOAV( DAOURI, POAVURI, msg.sender, block.timestamp, _FanToken);
            return true;
        }
        function mint(string memory POAVURI) public returns (bool) {
           _mintPOAV( "",POAVURI, msg.sender, block.timestamp, 0);
            return true;
        }
        function changeStatus(string memory POAVURI, address _subject, Status _status) public returns (bool) {
            require(issuersOfPOAV[POAVURI]==msg.sender, "Does not have access");
            require(statusOfPOAVSubject[POAVURI][_subject] != _status, "There is no change of state");
            statusOfPOAVSubject[POAVURI][_subject] = _status;
            emit POAVStatusChanged(POAVURI, _subject, _status);
            return true;
        }
        function burn(string memory POAVURI) public returns (bool) {
            require(issuersOfPOAV[POAVURI]==msg.sender, "Does not have access");
            delete issuersOfPOAV[POAVURI];
            delete validFromOfPOAV[POAVURI] ;
            return true;
        }
        function close(string memory POAVURI) public returns (bool) {
            require(issuersOfPOAV[POAVURI]==msg.sender, "Does not have access");
            issuersOfPOAV[POAVURI] = address(0);
            return true;
        }
        ...
    }