SDI for dapp developers

This page describes how to interact with the x/compliance module in order to get relevant information about the verified users and issuers.

If you need help on how to get your contract verified as an issuer and start verifying users, you can read check this section: https://swisstronik.gitbook.io/swisstronik-docs/development/swisstronik-cli/for-sdi-issuers

Examples of how to interact with Swisstronik smart contracts using different frontend libraries is available here: https://github.com/SigmaGmbH/swisstronik-dapp-template

Interacting with the x/compliance module using the SwisstronikJS SDK

You'll need to install @swisstronik/sdk npm package

import { SwisstronikStargateClient } from "@swisstronik/sdk";

const client = await SwisstronikStargateClient.connect(
    "https://rpc.testnet.swisstronik.com"
);

Getting address details

It returns details for a verified address in the x/compliance module. You can use this method for both verified issuers and verified users.

const address = "swtr...";
const addressDetails = await client.queryAddressDetails(address);

The result contains information about whether the address is verified as an issuer or not, array of issuers verifications or if the verification has been revoked. Please, note that if the user address is verified by an issuer, the isVerified attribute will be false since it's true just for verified issuers. If the user is verified by an issuer, the verification array will contain elements such as the verification Id, verification type and issuer address.

Getting Issuer details

It returns issuer details by its address (name, description, url, logo and legal entity).

const issuerAddress = "swtr...";
const issuerDetails = await client.queryIssuerDetails(issuerAddress);

Getting verification details

It returns verification details by its Id (verification type, expiration timestamp, issuance timestamp, issuer address, etc.).

const verificationID = "AKsIPk2b...";
const verificationDetails = await client.queryVerificationDetails(verificationID);

Getting verified address list

It returns the list of verified addresses details for both users and issuers.

const addressList = await client.queryAddressList();

Getting Issuer list

It returns the list of Issuer details.

const issuerList = await client.queryIssuerList();

Getting verification details list

It returns the list of Verification details.

const verificationList = await client.queryVerificationList();

Working demo on https://sigmagmbh.github.io/swisstronik-issuer-registry-frontend/

Interacting with the x/compliance module using Solidity

In order to get relevant x/compliance information, there's a contract deployed and ready to be used called SWTRProxy. Its code and latest address is deployed available on https://github.com/SigmaGmbH/swisstronik-sdi-contracts.

For interacting with the SWTRProxy contract in your solidity contract, you can install the @swisstronik/sdi-contracts npm package and use the ISWTRProxy interface in your contract.

How to import and initialize the SWTRProxy

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {ISWTRProxy} from "@swisstronik/sdi-contracts/contracts/interfaces/ISWTRProxy.sol";

contract Sample {
    ISWTRProxy public swtrProxy;
    
    constructor(ISWTRProxy _swtrProxy) { swtrProxy = _swtrProxy; }

}

From there you can call any function available in the ISWTRProxy interface

Getting list of verified Issuers

uint256 totalIssuers = swtrProxy.issuerRecordCount();
ISWTRProxy.Issuer[] memory issuers;

if(totalIssuers > 0) {
    issuers = swtrProxy.listIssuersRecord(0, totalIssuers); //pagination
}

It'll return issuers name, address, and version.

Get issuer by address

ISWTRProxy.Issuer memory issuer = swtrProxy.getIssuerRecordByAddress(0x123..);

Get Issuer Addresses by name and versions

There may be multiple versions/addresses for the same issuer name

uint32[] memory versions; //add your versions

address[] memory issuerAddresses = swtrProxy.getIssuerAddressesByNameAndVersions("Quadrata", versions);

The addresses are sorted in the same order the versions are passed.

Checking if an user is verified with a specific verification type by any issuer

bool isVerified = swtrProxy.isUserVerified(
    0x123.., //user address
    ISWTRProxy.VerificationType.VT_KYC
);

Checking if an user is verified with a specific verification type by specific issuers

address[] memory allowedIssuers; //add your issuers address

bool isVerifiedByIssuers = swtrProxy.isUserVerifiedBy(
    0x123.., //user address
    ISWTRProxy.VerificationType.VT_KYC,
    allowedIssuers
);

Getting verification details

bytes memory verificationId; // add your verification id

ISWTRProxy.VerificationData memory verificationDetails = swtrProxy.getVerificationDataById(
    0x123, // user address
    0x456, // issuer address
    verificationId
);

Getting verification details list

ISWTRProxy.VerificationData[] memory verificationList = swtrProxy.listVerificationData(
   0x123, // user address
   0x456, // issuer address
);

Checking if user has a specific verification type

bool passed = swtrProxy.passedVerificationType(
   0x123, // user address
   0x456, // issuer address
   ISWTRProxy.VerificationType.VT_HUMANITY // verification type
);

You can find the list of available functions here: https://github.com/SigmaGmbH/swisstronik-sdi-contracts

Solidity Examples

Getting an image url ONLY IF the user is has a specific verification type issued by a specific issuer

In this example only users with the VT_HUMANITY verification type issued by the specified issuer are able to view the image set by the owner. Note due to the Intel SGX, there's no other way to view the image.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ISWTRProxy} from "@swisstronik/sdi-contracts/contracts/interfaces/ISWTRProxy.sol";

enum AdapterType {
    Quadrata,
    WorldCoin
}

contract Sample is Ownable, EIP712 {
    string internal imageUrl; // Thanks to Intel SGX encryption, there's no way to know the value of this variable by checking the storage slot
    ISWTRProxy public swtrProxy;

    constructor(
        ISWTRProxy _swtrProxy
    ) EIP712("Sample Dapp", "1") Ownable(msg.sender) {
        swtrProxy = _swtrProxy;
    }

    function setImageUrl(string memory _imageUrl) public onlyOwner {
        imageUrl = _imageUrl;
    }

    function getImageUrl(
        bytes calldata signature
    ) public view returns (string memory) {
        require(bytes(imageUrl).length > 0, "Image URL not set");

        address userAddress = recoverSigner(signature);
  
        uint32[] memory versions = new uint32[](1);
        versions[0] = 1;
        
        address[] memory allowedIssuers = swtrProxy.getIssuerAddressesByNameAndVersions(
            "Worldcoin",
            versions
        );

        bool isVerified = swtrProxy.isUserVerifiedBy(
            userAddress,
            ISWTRProxy.VerificationType.VT_HUMANITY,
            allowedIssuers
        );

        require(isVerified, "User not verified");

        return imageUrl;
    }

    function recoverSigner(
        bytes calldata signature
    ) public view returns (address) {
        bytes32 structHash = keccak256(
            abi.encode(
                keccak256("Obj(string contents)"),
                keccak256("Sample Verification")
            )
        );

        bytes32 digest = _hashTypedDataV4(structHash);

        return ECDSA.recover(digest, signature);
    }
}

Getting decoded original data of the verification by user address

In thes example, we get the decoded original data of the user verification


function decodeQuadrataPassportV1OriginalData(
    address userAddress
)
    public
    view
    returns (
        uint8 aml,
        string memory country,
        string memory did,
        bool isBusiness,
        bool investorStatus
    )
    {
        uint32[] memory versions = new uint32[](1);
        versions[0] = 1;
        address issuerAddress = swtrProxy.getIssuerAddressesByNameAndVersions(
            "Quadrata",
            versions
        )[0];

        ISWTRProxy.VerificationData[] memory verificationList = swtrProxy
            .listVerificationData(userAddress, issuerAddress);

        require(verificationList.length > 0, "No verification data found");

        (aml, country, did, isBusiness, investorStatus) = swtrProxy
            .decodeQuadrataPassportV1OriginalData(
                verificationList[0].originalData
            );
    }

function decodeWorldcoinV1OriginalData(
    address userAddress
)
    public
    view
    returns (
        string memory merkle_root,
        string memory nullifier_hash,
        string memory proof,
        string memory verification_level
    )
    {
        uint32[] memory versions = new uint32[](1);
        versions[0] = 1;
        address issuerAddress = swtrProxy.getIssuerAddressesByNameAndVersions(
            "Worldcoin",
            versions
        )[0];

        ISWTRProxy.VerificationData[] memory verificationList = swtrProxy
            .listVerificationData(userAddress, issuerAddress);

        require(verificationList.length > 0, "No verification data found");

        (
            merkle_root,
            nullifier_hash,
            proof,
            verification_level
        ) = swtrProxy.decodeWorldcoinV1OriginalData(
            verificationList[0].originalData
        );
}

Typescript examples

Using web3.js and Swisstronik plugin order to check if an user has a specific verification type issued by a specific issuer

You'll need to install the plugin with npm i @swisstronik/web3-plugin-swisstronik

import { SwisstronikPlugin } from "@swisstronik/web3-plugin-swisstronik";
import { verificationTypes } from "@swisstronik/sdk/compliance/verificationDetails";
import { Web3 } from "web3";

const web3 = new Web3(walletProvider);
web3.registerPlugin(new SwisstronikPlugin("https://json-rpc.testnet.swisstronik.com"));

const contract = new web3.eth.Contract(ABI, CONTRACT_ADDRESS); // SWTRProxy ABI and Contract address

const verificationType = "VT_HUMANITY";

const issuerAddress = (
    await contract
        .methods
        .getIssuerAddressesByNameAndVersions("Quadrata", [1])
        .call()
) as string[][0];

const isVerified: boolean = contract.methods.isUserVerifiedBy(
    "0x123", //user address,
    verificationTypes.indexOf(verificationType), //enum
    issuerAddress
).call();

console.log(isVerified);

Getting decoded original data of the verification by user address

  const decodeQuadrataPassportV1OriginalData = async (userAddress: string) => {
    const addressDetails = await client?.queryAddressDetails(userAddress);

    const verificationsList = addressDetails?.verifications;

    const issuerAddress = (await contract.methods
      .getIssuerAddressesByNameAndVersions("Quadrata", [1])
      .call()) as string[][0];

    const verificationId = verificationsList?.find(
      (v) => v.issuerAddress === issuerAddress
    )?.verificationId;

    const verificationDetails = await client?.queryVerificationDetails(
      verificationId!
    );

    // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
    const originalData = "0x"+Buffer.from(verificationDetails?.originalData!,
      "base64"
    ).toString("hex");

    const { aml, country, did, isBusiness, investorStatus } =
    web3.eth.abi.decodeParameters(
      [
        {
          name: "aml",
          type: "uint8",
        },
        {
          name: "country",
          type: "string",
        },
        {
          name: "did",
          type: "string",
        },
        {
          name: "isBusiness",
          type: "bool",
        },
        {
          name: "investorStatus",
          type: "bool",
        },
      ],
      originalData
    );

  };

  const decodeWorldcoinV1OriginalData = async (userAddress: string) => {
    const addressDetails = await client?.queryAddressDetails(userAddress);

    const verificationsList = addressDetails?.verifications;

    const issuerAddress = (await contract.methods
      .getIssuerAddressesByNameAndVersions("Worldcoin", [1])
      .call()) as string[][0];

    const verificationId = verificationsList?.find(
      (v) => v.issuerAddress === issuerAddress
    )?.verificationId;

    const verificationDetails = await client?.queryVerificationDetails(
      verificationId!
    );

    // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
    const originalData = "0x"+Buffer.from(verificationDetails?.originalData!,
      "base64"
    ).toString("hex");

    const { merkle_root, nullifier_hash, proof, verification_level } =
    web3.eth.abi.decodeParameters(
      [
        {
          name: "merkle_root",
          type: "string",
        },
        {
          name: "nullifier_hash",
          type: "string",
        },
        {
          name: "proof",
          type: "string",
        },
        {
          name: "verification_level",
          type: "string",
        },
      ],
      originalData
    );
  };

Note that via the SDK the addresses are specified in Bech32 (cosmos) format, and in solidity in ETH format. In some situations you may want/need to convert from Bech32 to ETH format. Or the other way around. You can do it with the @swisstronik/utils package:

import { ethAddressToBech32, bech32toEthAddress } from "@swisstronik/utils";

Last updated