> For the complete documentation index, see [llms.txt](https://docs.quadrata.com/passport-issuer/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.quadrata.com/passport-issuer/integration-developers/attesting-attributes/2.-api-response.md).

# 2. API Response

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

{% hint style="info" %}
[Attributes](/passport-issuer/additional-information/passport-attributes.md) in the Quadrata Passport are standardized to enable interoperability between issuers.
{% endhint %}

### 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&#x20;

#### attrKeys

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

{% hint style="warning" %}
This list has to be the same length as *attrValues* and *attrTypes* and in the same exact order
{% endhint %}

{% tabs %}
{% tab title="Nodejs" %}

```javascript

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)
```

{% endtab %}

{% tab title="Python" %}

```python
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
```

{% endtab %}
{% endtabs %}

#### attrValues

List of attributes values. See [Passport Attributes](/passport-issuer/additional-information/passport-attributes.md) for more information.

{% hint style="warning" %}
This list has to be the same length as *attrValues* and *attrTypes* and in the same exact order
{% endhint %}

#### attrTypes

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

{% hint style="warning" %}
This list has to be the same length as *attrValues* and *attrTypes* and in the same exact order
{% endhint %}

#### fee

Fee in the native token ($ETH for Ethereum, $MATIC for Polygon) for attaching the new [attributes](/passport-issuer/additional-information/passport-attributes.md) being attested by the issuers. The fee is in [Wei](https://academy.binance.com/en/glossary/wei).

{% hint style="info" %}
100% of the fee is redistributed to the issuer.
{% endhint %}

#### 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

Blockchain Network chain Id (ex: 1 for Ethereum Mainnet, 137 for Polygon). \
See [list of chain ID](https://chainlist.org/).

#### signature

ECDSA Signature signed by the issuer to generate a verifiable proof of the [attributes](/passport-issuer/additional-information/passport-attributes.md) attested

{% tabs %}
{% tab title="Nodejs" %}

```javascript
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
);
```

{% endtab %}

{% tab title="Python" %}

```python
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()
```

{% endtab %}
{% endtabs %}

#### did (Optional)&#x20;

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