@galliun/sofi-sdk
Version:
SDK for interacting with the Galliun SocialFi protocol
794 lines (690 loc) • 23.6 kB
text/typescript
import { bcs } from '@mysten/sui/bcs';
import type { SuiTransactionBlockResponse } from '@mysten/sui/client';
import { Transaction } from '@mysten/sui/transactions';
import { ProfileModule } from './ProfileModule';
import type { BaseManagerArgs, CommonOptions } from '../base/BaseManager.js';
import { BaseManager } from '../base/BaseManager.js';
import {
devInspectAndGetReturnValues,
fetchAllDynamicFields,
getCoinOfValue,
objectArg,
ObjectInput,
} from '../util.js';
import type { ProfileTxParser } from './ProfileTxParser.js';
import {
AcceptedCoinTypes,
parseAcceptedCoinTypes,
parseFavourites,
parseLinks,
parsePoints,
parseProfileObj,
Profile,
} from '../SoFiObjects.js';
/**
* Options for profile operations
*/
type ProfileOptions = CommonOptions & {
dryRun?: boolean;
sender?: string;
};
/**
* Arguments for ProfileManager constructor
* @property txParser - Transaction parser for profile operations
*/
type ProfileManagerArgs = BaseManagerArgs & {
txParser: ProfileTxParser;
};
export class ProfileManager extends BaseManager {
public txParser: ProfileTxParser;
constructor(args: ProfileManagerArgs) {
super(args);
this.txParser = args.txParser;
}
/**
* Fetches a profile by address
* @param address The address of the profile to fetch
* @returns The profile object or null if not found
*/
public async fetchProfile(address: string): Promise<Profile | null> {
try {
const resp = await this.suiClient.getObject({
id: address,
options: {
showContent: true,
showType: true,
},
});
const profile = parseProfileObj(resp);
if (profile?.username) {
const links = await fetchAllDynamicFields(this.suiClient, profile.links_id);
const linksObj = await this.suiClient.multiGetObjects({
ids: links.map((c) => c.objectId),
options: { showContent: true },
});
const favourites = await fetchAllDynamicFields(this.suiClient, profile.favourites_id);
const favouritesObj = await this.suiClient.multiGetObjects({
ids: favourites.map((c) => c.objectId),
options: { showContent: true },
});
const acceptedCoinTypes = await fetchAllDynamicFields(
this.suiClient,
profile.accepted_coin_types_id,
);
const acceptedCoinTypesObj = await this.suiClient.multiGetObjects({
ids: acceptedCoinTypes.map((c) => c.objectId),
options: { showContent: true },
});
const pointsObj = await this.suiClient.getObject({
id: profile.points_id,
options: { showContent: true },
});
const linksData = parseLinks(linksObj);
const favouritesData = parseFavourites(favouritesObj);
const pointsData = parsePoints(pointsObj);
const acceptedCoinTypesData = parseAcceptedCoinTypes(acceptedCoinTypesObj);
profile.links = linksData;
profile.points = pointsData;
profile.accepted_coin_types = acceptedCoinTypesData;
profile.favourites = favouritesData;
return profile;
} else {
console.error('Error fetching profile: user not found');
return null;
}
} catch (error) {
console.error('Error fetching profile:', error);
return null;
}
}
/**
* Fetches multiple profiles by addresses
* @param addresses Array of profile addresses to fetch
* @returns Array of profile objects
*/
public async fetchProfiles(addresses: string[]): Promise<Array<Profile | null>> {
const results: Array<Profile | null> = [];
const responses = await this.suiClient.multiGetObjects({
ids: addresses,
options: { showContent: true },
});
for (const resp of responses) {
const profile = parseProfileObj(resp);
if (profile?.username) {
const links = await fetchAllDynamicFields(this.suiClient, profile.links_id);
const linksObj = await this.suiClient.multiGetObjects({
ids: links.map((c) => c.objectId),
options: { showContent: true },
});
const favourites = await fetchAllDynamicFields(this.suiClient, profile.favourites_id);
const favouritesObj = await this.suiClient.multiGetObjects({
ids: favourites.map((c) => c.objectId),
options: { showContent: true },
});
const pointsObj = await this.suiClient.getObject({
id: profile.points_id,
options: { showContent: true },
});
const acceptedCoinTypes = await fetchAllDynamicFields(
this.suiClient,
profile.accepted_coin_types_id,
);
const acceptedCoinTypesObj = await this.suiClient.multiGetObjects({
ids: acceptedCoinTypes.map((c) => c.objectId),
options: { showContent: true },
});
const linksData = parseLinks(linksObj);
const favouritesData = parseFavourites(favouritesObj);
const pointsData = parsePoints(pointsObj);
const acceptedCoinTypesData = parseAcceptedCoinTypes(acceptedCoinTypesObj);
profile.links = linksData;
// profile.followers = followersData;
// profile.following = followingData;
profile.points = pointsData;
profile.accepted_coin_types = acceptedCoinTypesData;
profile.favourites = favouritesData;
results.push(profile);
}
}
return results;
}
/**
* Creates a new profile
* @param username The username of the profile
* @param displayName The display name of the profile
* @param bio The bio text of the profile
* @param profilePicture The profile picture URL
* @param backgroundPicture The background picture URL
* @param profileRegistry The object ID of the profile registry
* @param options Optional parameters
* @returns Transaction response and created profile object
* @throws Error if profile creation fails
*/
public async createProfile(
username: string,
displayName: string,
bio: string,
profilePicture: string,
backgroundPicture: string,
profileRegistry: string,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjCreated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
const profileObj = ProfileModule.new(
tx,
this.packageId,
this.configId,
username,
displayName,
bio,
profilePicture,
backgroundPicture,
profileRegistry as ObjectInput,
);
tx.moveCall({
target: `${this.packageId}::profile::update_accepted_coin_type`,
arguments: [profileObj, objectArg(tx, this.configId)],
typeArguments: ['0x2::sui::SUI'],
});
tx.moveCall({
target: `${this.packageId}::profile::share`,
arguments: [profileObj],
typeArguments: [],
});
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjCreated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Updates an existing profile
* @param profileId The ID of the profile to update
* @param name The new name of the profile
* @param avatar The new avatar URL of the profile
* @param options Optional parameters
* @returns Transaction response
*/
public async updateProfile(
profile: ObjectInput,
display_name: string,
bio: string,
profile_picture: string,
background_picture: string,
accept_tips: boolean,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.update(
tx,
this.packageId,
profile,
display_name,
bio,
profile_picture,
background_picture,
accept_tips,
);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Updates the username of a profile
* @param profile The profile to update
* @param username The new username
* @param profileRegistry The profile registry
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async updateUsername(
profile: ObjectInput,
username: string,
profileRegistry: string,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.updateUsername(
tx,
this.packageId,
this.configId,
profile,
username,
profileRegistry as ObjectInput,
);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Buys premium for a profile
* @param profile The profile to update
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async buyPremium(
profile: ObjectInput,
coin_type: string,
amount: bigint,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
const [payment_coin] = await getCoinOfValue(this.suiClient, tx, sender, coin_type, amount);
ProfileModule.buyPremium(
tx,
this.packageId,
profile,
this.configId,
coin_type,
payment_coin,
);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Updates an existing link or adds a new one if it doesn't exist
* @param profile The profile to update
* @param name The name of the link to update or add
* @param url The URL for the link
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async updateLinks(
profile: ObjectInput,
name: string[],
url: string[],
media: string[],
position: number[],
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.updateLinks(tx, this.packageId, profile, name, url, media, position);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Removes a link from the profile
* @param profile The profile to update
* @param name The name of the link to remove
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async removeLink(
profile: ObjectInput,
name: string,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.removeLink(tx, this.packageId, profile, name);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Adds an accepted coin type to the profile
* @param profile The profile to update
* @param coinType The coin type to add
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async updateAcceptedCoinType(
profile: ObjectInput,
coinType: string,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.updateAcceptedCoinType(tx, this.packageId, profile, this.configId, coinType);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Removes an accepted coin type from the profile
* @param profile The profile to update
* @param coinType The coin type to remove
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async removeAcceptedCoinType(
profile: ObjectInput,
coinType: string,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.removeAcceptedCoinType(tx, this.packageId, profile, this.configId, coinType);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Checks if a coin type is accepted by a profile
* @param profile The profile to check
* @param coinType The coin type to check
* @param options Optional parameters
* @returns Whether the coin type is accepted
*/
public async isAcceptedCoinType(
profile: ObjectInput,
coinType: string,
options?: ProfileOptions,
): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isAcceptedCoinType(tx, this.packageId, profile, coinType);
const result = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]], sender);
return result[0][0];
}
/**
* Gets the accepted coin types of a profile
* @param profile The profile to get the accepted coin types from
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async getAcceptedCoinTypes(profile: string): Promise<AcceptedCoinTypes[]> {
try {
const fullProfile = await this.suiClient.getObject({
id: profile,
options: { showContent: true },
});
const profileObj = parseProfileObj(fullProfile);
const acceptedCoinsId = profileObj?.accepted_coin_types_id;
if (acceptedCoinsId) {
const coins = await fetchAllDynamicFields(this.suiClient, acceptedCoinsId);
const acceptedCoinTypesObj = await this.suiClient.multiGetObjects({
ids: coins.map((c) => c.objectId),
options: { showContent: true },
});
return parseAcceptedCoinTypes(acceptedCoinTypesObj);
}
if (!fullProfile) {
throw new Error('Failed to find profile');
}
return [];
} catch (error) {
console.error('Error fetching accepted coin types:', error);
return [];
}
}
/**
* Toggles whether a profile accepts tips
* @param profile The profile to update
* @param options Optional parameters
* @returns Transaction response and updated profile object
*/
public async toggleAcceptTips(
profile: ObjectInput,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.toggleAcceptTips(tx, this.packageId, profile);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
/**
* Checks if a profile is premium
* @param profile The profile to check
* @param options Optional parameters
* @returns Whether the profile is premium
*/
public async isPremium(profile: string): Promise<boolean> {
const profileObj = await this.fetchProfile(profile);
if(!profileObj) {
throw new Error('Profile not found');
}
return profileObj.premium > Date.now();
}
/**
* Checks if a username is valid
* @param username The username to check
* @param options Optional parameters
* @returns Whether the username is valid
*/
public async isValidUsername(username: string, options?: ProfileOptions): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isValidUsername(tx, this.packageId, username);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]], sender);
return results[0][0];
}
/**
* Checks if a profile is unique for the sender
* @param registry The username registry
* @param options Optional parameters
* @returns Whether the profile is unique
*/
public async isUniqueProfile(registry: ObjectInput, options?: ProfileOptions): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isUniqueProfile(tx, this.packageId, registry);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]], sender);
return results[0][0];
}
/**
* Checks if a username is reserved
* @param username The username to check
* @param options Optional parameters
* @returns Whether the username is reserved
*/
public async isReservedWord(username: string, options?: ProfileOptions): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isReservedWord(tx, this.packageId, username, this.configId);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]], sender);
return results[0][0];
}
/**
* Checks if a username is unique
* @param username The username to check
* @param registry The username registry
* @param options Optional parameters
* @returns Whether the username is unique
*/
public async isUniqueUsername(
username: string,
registry: ObjectInput,
options?: ProfileOptions,
): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isUniqueUsername(tx, this.packageId, username, registry);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]], sender);
return results[0][0];
}
/**
* Checks if a profile is accepting tips
* @param profile The profile to check
* @param options Optional parameters
* @returns Whether the profile is accepting tips
*/
public async isAcceptingTips(profile: ObjectInput, options?: ProfileOptions): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isAcceptingTips(tx, this.packageId, profile);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]], sender);
return results[0][0];
}
/**
* Finds a profile by username
* @param username The username to search for
* @param registry The username registry
* @param options Optional parameters
* @returns The profile ID
*/
public async findProfileByUsername(username: string, registry: ObjectInput): Promise<string> {
const tx = new Transaction();
ProfileModule.findProfileByUsername(tx, this.packageId, username, registry);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Address]]);
return results[0][0];
}
/**
* Finds a profile by address
* @param address The address to search for
* @param registry The username registry
* @param options Optional parameters
* @returns The profile ID
*/
public async findProfileByAddress(address: string): Promise<string> {
const tx = new Transaction();
ProfileModule.findProfileByAddress(tx, this.packageId, address, this.profileRegistryId);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Address]]);
return results[0][0];
}
/**
* Finds a profile by address
* @param address The address to search for
* @param registry The username registry
* @param options Optional parameters
* @returns The profile ID
*/
public async hasProfile(address: string): Promise<boolean> {
const tx = new Transaction();
ProfileModule.hasProfile(tx, this.packageId, address, this.profileRegistryId);
const results = await devInspectAndGetReturnValues(this.suiClient, tx, [[bcs.Bool]]);
return results[0][0];
}
/**
* Gets all links from a profile
* @param profile The profile to get links from
* @param options Optional parameters
* @returns Array of tuples containing link names and URLs
*/
public async getProfileLinks(
profile: ObjectInput,
options?: ProfileOptions,
): Promise<[string[], string[], string[], number[]]> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.getProfileLinks(tx, this.packageId, profile);
const results = await devInspectAndGetReturnValues(
this.suiClient,
tx,
[
[
bcs.vector(bcs.string()),
bcs.vector(bcs.string()),
bcs.vector(bcs.string()),
bcs.vector(bcs.u64()),
],
],
sender,
);
return [results[0][0], results[0][1], results[0][2], results[0][3]];
}
/**
* Checks if a profile is a favourite of another profile
* @param profile The profile to check
* @param favouriteId The ID of the profile to check if it is a favourite
* @param options Optional parameters
* @returns Whether the profile is a favourite
*/
public async isFavourite(
profile: ObjectInput,
favouriteId: string,
options?: ProfileOptions,
): Promise<boolean> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.isFavourite(tx, this.packageId, profile, favouriteId);
const results = await devInspectAndGetReturnValues(
this.suiClient,
tx,
[
[
bcs.Bool,
bcs.u64()
],
],
sender,
);
return results[0][0];
}
/**
* Unfollows a profile
* @param profile The profile that will unfollow
* @param targetProfile The profile to unfollow
* @param options Optional parameters
* @returns Transaction response
*/
public async toggleFavourite(
profile: ObjectInput,
favouriteId: string,
options?: ProfileOptions,
): Promise<{
resp: SuiTransactionBlockResponse;
profileObjChange: ReturnType<ProfileTxParser['extractProfileObjMutated']>;
}> {
const tx = new Transaction();
const sender = this.getSender(options?.sender);
ProfileModule.toggleFavourite(tx, this.packageId, profile, favouriteId);
const resp = await this.dryRunOrSignAndExecute(tx, options?.dryRun, sender);
const profileObjChange = this.txParser.extractProfileObjMutated(resp);
if (!profileObjChange) {
throw new Error('Failed to extract profile object from response');
}
return { resp, profileObjChange };
}
}