UNPKG

@galliun/sofi-sdk

Version:

SDK for interacting with the Galliun SocialFi protocol

794 lines (690 loc) 23.6 kB
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 }; } }