Passport Issuer
  • INTRODUCTION
    • Intro to Quadrata Passport
    • Intro to Passport Issuers
  • INTEGRATION (DEVELOPERS)
    • Issuer Permission
    • Attesting attributes
      • 1. API Request
      • 2. API Response
      • 3. Minting Passport
    • Querying Attributes
    • Burning Passports
    • On-going Monitoring
  • Additional Information
    • Passport Attributes
    • Smart Contracts
    • Issuer Toolkit Library
Powered by GitBook
On this page
  1. INTEGRATION (DEVELOPERS)
  2. Attesting attributes

2. API Response

Previous1. API RequestNext3. Minting Passport

Last updated 2 years ago

Once the verification by the issuer is completed (ex: KYC verification, AML verification, etc..), the issuers is responsible for generating a standardized API response.

in the Quadrata Passport are standardized to enable interoperability between issuers.

Response API Payload

{
  'contractParameters': {
     'account': '',
     'attrKeys': [],
     'attrValues': [],
     'attrTypes' [],
     'fee': '',
     'verifiedAt': '',
     'issuedAt': '',
     'chainId': '',
     'signature': '',
     'did': '',
  },
  
   // Remaining Issuer specific Response Payload
}

account

Wallet address of the user to verify

attrKeys

List of attribute identifiers linking the attribute type and the account.

This list has to be the same length as attrValues and attrTypes and in the same exact order


const { utils } = require("ethers");
import { BytesLike } from '@ethersproject/bytes';

const computeAttrKeys = (account: string, attrTypes: BytesLike[]) => {
    let attrKeys: string[] = [];
    let attrKey: str;
    
    attrTypes.forEach((attrType) => {
        // For attributes set at the wallet address level (ex: COUNTRY, IS_BUSINESS, DID)
        attrKey = utils.keccak256(
        utils.defaultAbiCoder.encode(["address", "bytes32"]),
        [account, attrType]
        );
    
        // ------ OR -------- //
    
        // For attributes grouped by DID (ex: AML)
        attrKey = utils.keccak256(
            utils.defaultAbiCoder.encode(["bytes32", "bytes32"]),
            [did, attrType]
        ); 
        attrKeys.push(attrKey)
    }
    
    return attrKeys;
}

const account = "0x......"; // User Wallet Address
const attrTypes = [utils.id("COUNTRY"), utils.id("AML")]; // List of attributes to attest

const attrKeys = computeAttrKeys(account, attrTypes)
from web3 import Web3  # type: ignore

def compute_attr_keys(accountAddress, attrTypes):
    checksum_account = bytes.fromhex(
        Web3.toChecksumAddress(accountAddress)[2:]
    ).rjust(32, b'\0')
    
    attrKeys = []

    for attrType in attrTypes:
        attrKey = Web3.solidityKeccak(
            ['bytes32', 'bytes32'], 
            [
                checksum_account, 
                attrType
            ]
        )
        attrKeys.append(attrKey)
        
    return attrKeys
    

account = "0x........"  # Wallet Address
attrTypes = [Web3.solidityKeccak(['bytes32'], ["AML".encode()]).hex()]
attrKeys = compute_attr_keys(account, attrTypes)
return attrKeys

attrValues

This list has to be the same length as attrValues and attrTypes and in the same exact order

attrTypes

List of attributes types (ex: [utils.id("COUNTRY"), utils.id("AML")])

This list has to be the same length as attrValues and attrTypes and in the same exact order

fee

100% of the fee is redistributed to the issuer.

verifiedAt

Unix Epoch (in seconds) representing the date when the attributes has been verified by the issuer.

issuedAt

Unix Epoch (in seconds) representing the date when the attributes signature has been generated - allowing the user to mint their passport

chainId

signature

const { Signer, DataHexString, utils, Wallet } = require("ethers");
import { BytesLike } from '@ethersproject/bytes';


export const signAttributes = async (
  accountAddress: string,
  issuer: typeof Signer,
  attrKeys: BytesLike[],
  attrValues: BytesLike[],
  verifiedAt: number,
  issuedAt: number,
  fee: any,
  passportAddress: string,
  chainId: number,
  did: BytesLike = utils.constants.HashZero,
): Promise<typeof DataHexString> => {
  const attrKeys: string[] = [];
  const attrValues: string[] = [];

  const hash = utils.keccak256(
    utils.defaultAbiCoder.encode(
      [
        "address",
        "bytes32[]",
        "bytes32[]",
        "bytes32",
        "uint256",
        "uint256",
        "uint256",
        "uint256",
        "address",
      ],
      [
        accountAddress,
        attrKeys,
        attrValues,
        did,
        verifiedAt,
        issuedAt,
        fee,
        chainId,
        passportAddress,
      ]
    )
  );

  const sig = await issuer.signMessage(utils.arrayify(hash));

  return sig;
};

const accountAddress = "0x......";  // Wallet address of the user
const issuer = new Wallet(ISSUER_PRIVATE_KEY); // Loading issuer account from their private key
const attrTypes = [utils.id("COUNTRY"), utils.id("AML")];  // List of attributes types
const attrKeys = computeAttrKeys(accountAddress, attrTypes); // List of attributes Identifiers
const attrValues = [utils.id("US"), utils.hexZeroPad("0x01", 32)]; // List of attributes values
const verifiedAt = Math.floor(new Date().getTime() / 1000) - 60; // we substract 60 seconds to avoid errors during minting passport with long block time
const issuedAt = Math.floor(new Date().getTime() / 1000) - 60; // we substract 60 seconds to avoid errors during minting passport with long block time
const passportAddress = "0x2e779749c40CC4Ba1cAB4c57eF84d90755CC017d";
const chainId = 1; // 1 for Ethereum Mainnet
const fee = utils.parseEther("0.001"); // Fee paid by the user to the issuer in then native blokcchain currency (ex: ETH for Ethereum, MATIC for Polygon)

const signature = await signAttributes(
  accountAddress,
  issuer,
  attrKeys,
  attrValues,
  verifiedAt,
  issuedAt, 
  fee,
  passportAddress,
  chainId
);
import codecs
import eth_abi
from web3 import Web3  # type: ignore
from eth_account.messages import encode_defunct

W3_TIMEOUT = 60
NETWORK_URI = "" # INFURA_RPC_NODE
ISSUER_PRIVATE_KEY = "" # ISSUER__PRIVATE_KEY] 

def _hexed_checksum_address(self, address) -> bytes:
    return bytes.fromhex(Web3.toChecksumAddress(address)[2:]).rjust(32, b'\0')

def generate_signature(
    account_address: str,
    attr_keys: str[],
    attr_values: str[],
    verified_at: int,
    issued_at: int,
    mint_fee: int,
    passport_address: str,
    chain_id: int,
    did: str = web3.constants.HASH_ZERO[2:]
) -> str:
    
    abiEncoded = eth_abi.encode_abi(
        [
            'bytes32',
            'bytes32[]',
            'bytes32[]',
            'bytes32',
            'uint256',
            'uint256',
            'uint256',
            'uint256',
            'bytes32',
        ],
        [
            _hexed_checksum_address(account_address),
            attr_keys,
            attr_values,
            did, 
            verified_at,
            issued_at,
            mint_fee, # fee in wei
            chain_id, # we will support mainnet/goerli or matic/mumbai
            _hexed_checksum_address(passport_address), # see our docs, this varies depending on env/chain
        ]
    )

    digest_to_sign = Web3.solidityKeccak(['bytes32'], [('0x' + abiEncoded.hex())]).hex()
    decoder = codecs.getdecoder('hex_codec')

    w3 = Web3(Web3.HTTPProvider(NETWORK_URI, request_kwargs={'timeout': W3_TIMEOUT}))
    return w3.eth.account.sign_message(
        encode_defunct(hexstr=encoded_msg),
        private_key=decoder(str.encode(ISSUER_PRIVATE_KEY))[0],
    ).signature.hex()

did (Optional)

The Decentralized Identifier for the wallet holders. This field is only required for passport issuers performing documentary KYC.

List of attributes values. See for more information.

Fee in the native token ($ETH for Ethereum, $MATIC for Polygon) for attaching the new being attested by the issuers. The fee is in .

Blockchain Network chain Id (ex: 1 for Ethereum Mainnet, 137 for Polygon). See .

ECDSA Signature signed by the issuer to generate a verifiable proof of the attested

Attributes
Passport Attributes
attributes
Wei
list of chain ID
attributes