Skip to content

Commit

Permalink
feat(user program): updated the app to use the new user program
Browse files Browse the repository at this point in the history
  • Loading branch information
iltumio committed Feb 15, 2022
1 parent 06aa87e commit 22ab14f
Show file tree
Hide file tree
Showing 11 changed files with 612 additions and 87 deletions.
5 changes: 3 additions & 2 deletions components/views/friends/add/Add.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { debounce } from 'lodash'
import { Friend } from '~/types/ui/friends'
import ServerProgram from '~/libraries/Solana/ServerProgram/ServerProgram'
import SolanaManager from '~/libraries/Solana/SolanaManager/SolanaManager'
import UsersProgram from '~/libraries/Solana/UsersProgram/UsersProgram'
export default Vue.extend({
components: {
Expand Down Expand Up @@ -68,9 +69,9 @@ export default Vue.extend({
this.error = ''
try {
const $SolanaManager: SolanaManager = Vue.prototype.$SolanaManager
const serverProgram: ServerProgram = new ServerProgram($SolanaManager)
const usersProgram: UsersProgram = new UsersProgram($SolanaManager)
const friend = await serverProgram.getUser(new PublicKey(accountID))
const friend = await usersProgram.getUserInfo(accountID)
if (!friend) {
this.error = this.$t('friends.not_found') as string
return
Expand Down
2 changes: 2 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const Config = {
friendsProgramId: 'BxX6o2HG5DWrJt2v8GMSWNG2V2NtxNbAUF3wdE5Ao5gS',
defaultCommitment: 'confirmed' as Commitment,
defaultPreflightCommitment: 'confirmed' as Commitment,
usersPrograId: '7MaC2xrAmmFsuRBEkD6BEL3eJpXCmaikYhLM3eKBPhAH',
groupchatsPrograId: 'bJhvwTYCkQceANgeShZ4xaxUqEBPsV8e1NgRnLRymxs',
},
// Realms are just different chains we support
realms: [
Expand Down
22 changes: 11 additions & 11 deletions libraries/Solana/FriendsProgram/FriendsProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,17 +637,17 @@ export default class FriendsProgram extends EventEmitter {

const { connection } = this.solana

const userAccount = this.solana.getUserAccount()
if (!userAccount) {
const payerAccount = this.solana.getActiveAccount()
if (!payerAccount) {
throw new Error('User account not found')
}

const fromKeyAndStatus = base58(
Buffer.from([...userAccount.publicKey.toBytes(), status]),
Buffer.from([...payerAccount.publicKey.toBytes(), status]),
)

const statusAndToKey = base58(
Buffer.from([status, ...userAccount.publicKey.toBytes()]),
Buffer.from([status, ...payerAccount.publicKey.toBytes()]),
)

const outgoing = await connection.getProgramAccounts(FRIENDS_PROGRAM_ID, {
Expand Down Expand Up @@ -693,8 +693,8 @@ export default class FriendsProgram extends EventEmitter {
}

const { connection } = this.solana
const userAccount = this.solana.getUserAccount()
if (!userAccount) {
const payerAccount = this.solana.getActiveAccount()
if (!payerAccount) {
throw new Error('User account not found')
}

Expand All @@ -703,7 +703,7 @@ export default class FriendsProgram extends EventEmitter {
// formatted that way
// [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)]
const incomingRequestBytes = base58(
Buffer.from([FriendStatus.PENDING, ...userAccount.publicKey.toBytes()]),
Buffer.from([FriendStatus.PENDING, ...payerAccount.publicKey.toBytes()]),
)

const incomingRequestsFilter: GetProgramAccountsFilter = {
Expand All @@ -723,7 +723,7 @@ export default class FriendsProgram extends EventEmitter {
// This filter checks the sender public key (our) and the status
// [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)]
const newFriendFromOutgoingBytes = base58(
Buffer.from([...userAccount.publicKey.toBytes(), FriendStatus.ACCEPTED]),
Buffer.from([...payerAccount.publicKey.toBytes(), FriendStatus.ACCEPTED]),
)

const newFriendFromOutgoingFilter: GetProgramAccountsFilter = {
Expand All @@ -741,7 +741,7 @@ export default class FriendsProgram extends EventEmitter {
// This filter checks the sender public key (our) and the status
// [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)]
const friendRequestDeniedBytes = base58(
Buffer.from([...userAccount.publicKey.toBytes(), FriendStatus.REFUSED]),
Buffer.from([...payerAccount.publicKey.toBytes(), FriendStatus.REFUSED]),
)

const friendRequestDeniedFilter: GetProgramAccountsFilter = {
Expand All @@ -761,7 +761,7 @@ export default class FriendsProgram extends EventEmitter {
// This filter checks the sender public key (our) and the status
// [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)]
const friendRequestRemovedBytes = base58(
Buffer.from([...userAccount.publicKey.toBytes(), FriendStatus.REMOVED]),
Buffer.from([...payerAccount.publicKey.toBytes(), FriendStatus.REMOVED]),
)

const friendRequestRemovedFilter: GetProgramAccountsFilter = {
Expand All @@ -776,7 +776,7 @@ export default class FriendsProgram extends EventEmitter {
)

const friendRequestRemovedMirroredBytes = base58(
Buffer.from([FriendStatus.REMOVED, ...userAccount.publicKey.toBytes()]),
Buffer.from([FriendStatus.REMOVED, ...payerAccount.publicKey.toBytes()]),
)

const friendRequestRemovedMirroredFilter: GetProgramAccountsFilter = {
Expand Down
235 changes: 235 additions & 0 deletions libraries/Solana/UsersProgram/UsersProgram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { EventEmitter } from 'events'
import { Program, Wallet, web3, utils, Provider } from '@project-serum/anchor'
import { Users, IDL } from './UsersProgram.types'

import Solana from '~/libraries/Solana/SolanaManager/SolanaManager'
import { Config } from '~/config'

const { PublicKey, SystemProgram } = web3

export const USERS_PROGRAM_ID = new PublicKey(Config.solana.usersPrograId)

const userSeed = Buffer.from(utils.bytes.utf8.encode('user'))

export default class UsersProgram extends EventEmitter {
solana?: Solana
program?: Program<Users>
subscriptions?: { [eventName: string]: number }

constructor(solana: Solana) {
super()
if (solana) {
this.init(solana)
}
}

/**
* @method init
* Initializes the class with the SolanaManager instance
* @param solana SolanaManager instance
*/
init(solana: Solana) {
this.solana = solana

const payer = this._getPayer()

const provider = new Provider(this.solana.connection, new Wallet(payer), {
commitment: Config.solana.defaultCommitment,
})

this.program = new Program<Users>(
IDL,
USERS_PROGRAM_ID.toBase58(),
provider,
)
}

/**
* @method _userPDAPublicKey
* Computes a user PDA for the given Public Key
* @param address String version of the Public Key
* @returns the computed PDA
*/
protected async _userPDAPublicKey(address: string) {
const program = this._getProgram()

const user = new PublicKey(address)

return utils.publicKey.findProgramAddressSync(
[user.toBytes(), userSeed],
program.programId,
)
}

/**
* @method _getProgram
* Returns the anchor program instance for group chat
* @returns the anchor program instance
*/
protected _getProgram() {
if (!this.program) {
throw new Error('Group Chat Manager not initialized')
}

return this.program
}

/**
* @method _getPayer
* Retrieve the active account from Solana wallet
* @returns the payer account
*/
protected _getPayer() {
const payer = this.solana?.getActiveAccount()

if (!payer) {
throw new Error('Missing payer')
}

return payer
}

/**
* @method _getUpdateOpts
* Get multiple information useful for the update functions
* @returns program instance, payer account, user info and user PDA
*/
protected async _getUpdateOpts() {
const program = this._getProgram()
const payer = this._getPayer()

const userPDA = await this._userPDAPublicKey(payer.publicKey.toBase58())

const userInfo = await this.getCurrentUserInfo()

if (!userInfo) {
throw new Error('Non existent account')
}

return { program, payer, userPDA, userInfo }
}

/**
* @method create
* Create a new user
* @param name Username
* @param photoHash Profile picture IPFS hash
* @param statusMessage Status message string
*/
async create(name: string, photoHash: string, statusMessage: string) {
// Throws if the program is not set
const program = this._getProgram()

// Throws if the payer is not set
const payer = this._getPayer()

const userPDA = utils.publicKey.findProgramAddressSync(
[payer.publicKey.toBytes(), userSeed],
program.programId,
)

await program.rpc.create(name, photoHash, statusMessage, {
accounts: {
user: userPDA[0],
signer: payer.publicKey,
payer: payer.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [payer],
})
}

/**
* @method getUserInfo
* Fetch the user information from the user program
* @param address string representation of the Public key
* @returns the parsed user info
*/
async getUserInfo(address: string) {
const program = this._getProgram()

const userPDA = await this._userPDAPublicKey(address)

const userInfo = await program.account.user.fetchNullable(userPDA[0])

if (!userInfo) {
return null
}

return {
name: userInfo.name as string,
photoHash: userInfo.photoHash as string,
status: userInfo.status as string,
}
}

/**
* @method getCurrentUserInfo
* Returns the user info for the active account
* @returns the user info object for the current
*/
async getCurrentUserInfo() {
const payer = this._getPayer()

return this.getUserInfo(payer.publicKey.toBase58())
}

/**
* @method setName
* Allow the user to update the user name
* @param name user name
* @returns the transaction to update the status message
*/
async setName(name: string) {
const { program, userPDA, payer } = await this._getUpdateOpts()

return program.rpc.setName(name, {
accounts: {
user: userPDA[0],
signer: payer.publicKey,
payer: payer.publicKey,
},
signers: [payer],
})
}

/**
* @method setPhotoHash
* Allow the user to update the profile picture
* @param photoHash profile picture IPFS hash
* @returns the transaction to update the photo hash
*/
async setPhotoHash(photoHash: string) {
const { program, userPDA, payer } = await this._getUpdateOpts()

return program.rpc.setPhotoHash(photoHash, {
accounts: {
user: userPDA[0],
signer: payer.publicKey,
payer: payer.publicKey,
},
signers: [payer],
})
}

/**
* @method setStatusMessage
* Allow the user to update the status message
* @param statusMessage status message
* @returns the transaction to update the status message
*/
async setStatusMessage(statusMessage: string) {
const { program, userPDA, payer } = await this._getUpdateOpts()

return program.rpc.setStatus(statusMessage, {
accounts: {
user: userPDA[0],
signer: payer.publicKey,
payer: payer.publicKey,
},
signers: [payer],
})
}
}

export type UserInfo = Awaited<ReturnType<UsersProgram['getUserInfo']>>
Loading

0 comments on commit 22ab14f

Please sign in to comment.