On-going Monitoring
For a subset of attributes (ex: AML), the issuer is responsible to perform on-going monitoring and update the attribute values directly in the passport.
Only specific issuers are required to perform on-going monitoring.
Example (change of AML score)
import { ethers } from 'ethers';
import { BytesLike } from '@ethersproject/bytes';
const newAMLScore = hexZeroPad("0x05", 32); // new AML score
const accountToUpdate = "0x......."; // Wallet of the Passport holder to update
const issuer = new Wallet(ISSUER_PRIVATE_KEY); // Loading issuer account from their private key
const attrTypes = [utils.id("AML")]; // List of attributes types
const attrKeys = computeAttrKeys(accountAddress, attrTypes); // List of attributes Identifiers
const attrValues = [newAMLScore]; // 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"); // No fee to paid as this is an update performed by the issuer themselves
const did = ethers.constants.HashZero; // Leave empty
const signature = await signAttributes(
accountAddress,
issuer,
attrKeys,
attrValues,
verifiedAt,
issuedAt,
fee,
passportAddress,
chainId
)
const QUAD_PASSPORT_ABI = ''; // Can be retrieve at https://etherscan.io/address/0xebf9b4db6050517e402c578237326b793bf348ff#code
const passportContract = new ethers.Contract(
passportAddress,
QUAD_PASSPORT_ABI,
);
const tx = await passportContract.setAttributesIssuer(
accountAddress,
[
attrKeys,
attrValues,
attrTypes,
ethers.constants.HashZero, // Leave bytes32(0)
0, // Leave 0
verifiedAt,
issuedAt,
fee,
],
sigIssuer,
{
value: fee,
}
)
)
await tx.wait();
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;
};
`
W3_TIMEOUT = 60
ABI_PATH = "" # DIRECTORY CONTAINING ABI FILE
PASSPORT_ABI_FILE_NAME = "" # ABI FILE NAME
NETWORK_URI = "" # INFURA_RPC_NODE
PASSPORT_CONTRACT_ADDRESS = "" # PASSPORT ADDRESS
CHAIN_ID = 1 # CHAIN ID WHERE ATTESTATION WILL BE POSTED
GAS_LIMIT = 300000
ISSUER_PRIVATE_KEY = "" # ISSUER__PRIVATE_KEY
HEX_PADDING = 66
@dataclass(frozen=True)
class UpdateCreditScoreInputs:
target_address: str
token_id: int
new_value: str
verified_at: int
quad_did: str
issued_at: int = blockchain_timestamp_seconds()
# Update fee should be 0 so issuer does not have to pay for update
update_fee: int = 0
def attr_keys(self) -> List[bytes]:
# Need to modify CREDIT_SCORE to attribute type
return [Web3.solidityKeccak(['bytes32', 'bytes32'], [self.target_address, keccak('CREDIT_SCORE')])]
def attr_values(self) -> List[bytes]:
return [self.hexlified_value()]
def attr_types(self) -> List[str]:
return [keccak('CREDIT_SCORE')]
def hexlified_value(self) -> bytes:
return bytes.fromhex(f'{self.new_value:#0{HEX_PADDING}x}'[2:])
def attribute_setter_config(self) -> tuple:
return (
[key.hex() for key in self.attr_keys()],
[f'0x{val.hex()}' for val in self.attr_values()],
self.attr_types(),
self.quad_did,
self.token_id,
self.verified_at,
self.issued_at,
self.update_fee,
)
def update_message(self, chain_id) -> str:
abiEncoded = eth_abi.encode_abi(
[
'bytes32',
'bytes32[]',
'bytes32[]',
'bytes32',
'uint256',
'uint256',
'uint256',
'uint256',
'bytes32',
],
[
self.target_address,
self.attr_keys(),
self.attr_values(),
bytes.fromhex(self.quad_did[2:]),
self.verified_at,
self.issued_at,
self.update_fee,
chain_id,
bytes.fromhex(Web3.toChecksumAddress(PASSPORT_CONTRACT_ADDRESS)[2:]).rjust(
32, b'\0'
),
],
)
return Web3.solidityKeccak(['bytes32'], [('0x' + abiEncoded.hex())]).hex()
def keccak(value: str) -> str:
if not value:
raise ValueError('Attempted to keccak hash an empty string')
return Web3.solidityKeccak(['bytes32'], [value.encode()]).hex()
def _load_passport_abi(self) -> str:
with open(os.path.join(ABI_PATH, PASSPORT_ABI_FILE_NAME)) as f:
return json.load(f)
update_inputs = UpdateCreditScoreInputs(
target_address = '', # target wallet address for credit score update
token_id = 1, # configurable passport skin
new_value = '',# new value
verified_at = int(datetime.now(timezone.utc).timestamp()) - 60,
quad_did = web3.constants.HASH_ZERO,
issued_at = int(datetime.now(timezone.utc).timestamp()) - 60,
update_fee = 0 # Update fee should be 0 so issuer does not have to pay for update
)
w3 = Web3(Web3.HTTPProvider(NETWORK_URI, request_kwargs={'timeout': W3_TIMEOUT}))
contract = w3.eth.contract(
address=Web3.toChecksumAddress(PASSPORT_CONTRACT_ADDRESS),
abi=self._load_passport_abi(),
)
# Assumes no pending transaction
nonce = w3.eth.getTransactionCount(
Web3.toChecksumAddress(
w3.eth.account.privateKeyToAccount(ISSUER_PRIVATE_KEY).address))
transaction = contract.functions.setAttributesIssuer(
Web3.toChecksumAddress(update_inputs.target_address),
update_inputs.attribute_setter_config(),
update_inputs.update_message(CHAIN_ID),
).buildTransaction(
{
'gasPrice': w3.eth.gas_price,
'gas': GAS_LIMIT,
'nonce': nonce,
'chainId': CHAIN_ID,
}
)
decoder = codecs.getdecoder('hex_codec')
signed_tx = self.w3.eth.account.signTransaction(
transaction, private_key=decoder(str.encode(ISSUER_PRIVATE_KEY))[0]
)
tx_hash = self.w3.eth.sendRawTransaction(signed_tx.rawTransaction)
Last updated