@citizenwallet/sdk
Version:
An sdk to easily work with citizen wallet.
194 lines (158 loc) • 4.8 kB
text/typescript
import { hexlify, toUtf8Bytes, JsonRpcProvider, Contract, id } from "ethers";
import { type CommunityConfig } from "../config";
import { downloadJsonFromIpfs } from "../ipfs";
import profileContractAbi from "../abi/Profile.abi.json";
import dotenv from "dotenv";
import { addressToId, idToAddress } from "./utils";
import { PROFILE_ADMIN_ROLE } from "../utils/crypto";
dotenv.config();
export interface Profile {
account: string;
description: string;
image: string;
image_medium: string;
image_small: string;
name: string;
username: string;
}
export interface ProfileWithTokenId extends Profile {
token_id: string;
}
export const formatProfileImageLinks = (
ipfsUrl: string,
profile: Profile
): Profile => {
if (profile.image_small.startsWith("ipfs://")) {
profile.image_small = `${ipfsUrl}/${profile.image_small.replace(
"ipfs://",
""
)}`;
}
if (profile.image_medium.startsWith("ipfs://")) {
profile.image_medium = `${ipfsUrl}/${profile.image_medium.replace(
"ipfs://",
""
)}`;
}
if (profile.image.startsWith("ipfs://")) {
profile.image = `${ipfsUrl}/${profile.image.replace("ipfs://", "")}`;
}
return profile;
};
const padBytesWithSpace = (bytes: Uint8Array, length: number): Uint8Array => {
const spaceByte = new TextEncoder().encode(" ");
while (bytes.length < length) {
bytes = new Uint8Array([...spaceByte, ...bytes]);
}
return bytes;
};
export const formatUsernameToBytes32 = (username: string): string => {
return hexlify(padBytesWithSpace(toUtf8Bytes(username.replace("@", "")), 32));
};
export const getProfileFromId = async (
ipfsDomain: string,
config: CommunityConfig,
id: string
): Promise<ProfileWithTokenId | null> => {
const rpc = new JsonRpcProvider(config.primaryRPCUrl);
const contract = new Contract(
config.community.profile.address,
profileContractAbi,
rpc
);
try {
const address = idToAddress(BigInt(id));
const uri: string = await contract.getFunction("tokenURI")(address);
const profile = await downloadJsonFromIpfs<Profile>(ipfsDomain, uri);
return {
...formatProfileImageLinks(`https://${ipfsDomain}`, profile),
token_id: id,
};
} catch (error) {
console.error("Error fetching profile:", error);
return null;
}
};
export const getProfileFromAddress = async (
ipfsDomain: string,
config: CommunityConfig,
address: string
): Promise<ProfileWithTokenId | null> => {
const id = addressToId(address);
return getProfileFromId(ipfsDomain, config, id.toString());
};
export const getProfileFromUsername = async (
ipfsDomain: string,
config: CommunityConfig,
username: string
): Promise<ProfileWithTokenId | null> => {
const rpc = new JsonRpcProvider(config.primaryRPCUrl);
const contract = new Contract(
config.community.profile.address,
profileContractAbi,
rpc
);
try {
const formattedUsername = formatUsernameToBytes32(username);
const uri: string = await contract.getFunction("getFromUsername")(
formattedUsername
);
const profile = await downloadJsonFromIpfs<Profile>(ipfsDomain, uri);
const id = addressToId(profile.account);
return {
...formatProfileImageLinks(`https://${ipfsDomain}`, profile),
token_id: id.toString(),
};
} catch (error) {
console.error("Error fetching profile:", error);
return null;
}
};
export const hasProfileAdminRole = async (
config: CommunityConfig,
address: string
): Promise<boolean> => {
const rpc = new JsonRpcProvider(config.primaryRPCUrl);
const contract = new Contract(
config.community.profile.address,
profileContractAbi,
rpc
);
try {
const isAdmin = await contract.getFunction("hasRole")(
PROFILE_ADMIN_ROLE,
address
);
return isAdmin;
} catch (error) {
console.error("Error checking profile admin role:", error);
}
try {
const isOwner = await contract.getFunction("owner")();
return isOwner.toLowerCase() === address.toLowerCase();
} catch (error) {
console.error("Error checking profile owner:", error);
}
return false;
};
export const checkUsernameAvailability = async (
config: CommunityConfig,
username: string
): Promise<boolean> => {
const rpc = new JsonRpcProvider(config.primaryRPCUrl);
const contract = new Contract(
config.community.profile.address,
profileContractAbi,
rpc
);
try {
const formattedUsername = formatUsernameToBytes32(username);
const uri: string | null | undefined = await contract.getFunction(
"getFromUsername"
)(formattedUsername);
return uri === null || uri === undefined || uri === "";
} catch (error) {
console.error("Error checking username availability:", error);
return true;
}
};