From 0bcef89446b0625f8ffa57a01d7b98c5dee908de Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Mon, 21 Oct 2024 00:00:10 -0500 Subject: [PATCH] feat: profiles are managed by community --- anchor/Anchor.toml | 2 +- anchor/programs/pubkey-protocol/src/errors.rs | 4 +- .../src/instructions/community/mod.rs | 2 - .../src/instructions/identity/mod.rs | 5 - .../pubkey-protocol/src/instructions/mod.rs | 2 - .../src/instructions/profile/mod.rs | 6 + .../profile/profile_authority_add.rs | 6 +- .../profile/profile_authority_remove.rs | 6 +- .../instructions/profile/profile_create.rs | 11 +- .../profile_identity_add.rs} | 23 +- .../profile_identity_remove.rs} | 16 +- .../profile_identity_verify.rs} | 12 +- .../profile/profile_update_details.rs | 19 +- anchor/programs/pubkey-protocol/src/lib.rs | 26 +- .../pubkey-protocol/src/state/community.rs | 14 + .../pubkey-protocol/src/state/profile.rs | 13 +- anchor/src/pubkey-protocol-exports.ts | 13 +- anchor/target/idl/pubkey_protocol.json | 363 +++++++++--------- anchor/target/types/pubkey_protocol.ts | 363 +++++++++--------- .../tests/pubkey-protocol-community.spec.ts | 22 +- anchor/tests/pubkey-protocol-identity.spec.ts | 160 -------- anchor/tests/pubkey-protocol-profile.spec.ts | 351 ++++++++++++----- anchor/tests/utils/create-test-community.ts | 16 +- anchor/tests/utils/create-test-profile.ts | 26 +- cli/src/commands/get-community-command.ts | 2 +- cli/src/commands/get-provision-command.ts | 5 - cli/src/utils/get-config.ts | 3 - cli/src/utils/get-fee-payer-keypair.ts | 11 - sdk/src/lib/pubkey-protocol-sdk.ts | 140 ++++--- .../dev/data-access/convert-to-map.ts | 11 +- .../dev/data-access/dev-data-communities.ts | 3 +- web/src/app/features/dev/data-access/index.ts | 1 - .../pubkey-community/data-access/index.ts | 1 + .../data-access/pubkey-community-provider.tsx | 66 ++++ .../app/features/pubkey-community/ui/index.ts | 1 + .../pubkey-protocol-ui-community-select.tsx | 40 ++ .../data-access/use-mutation-add-identity.tsx | 9 +- .../use-mutation-create-profile.tsx | 6 +- .../use-mutation-profile-authority-add.tsx | 8 +- .../use-mutation-profile-authority-remove.tsx | 9 +- .../use-mutation-remove-identity.tsx | 8 +- .../use-mutation-update-avatar-url.tsx | 12 +- .../feature/pubkey-profile-feature-create.tsx | 8 +- .../feature/pubkey-profile-feature-detail.tsx | 5 +- .../feature/pubkey-profile-feature-search.tsx | 10 +- .../feature/pubkey-profile.routes.tsx | 55 ++- .../ui/pubkey-protocol-ui-identity.tsx | 47 ++- ...col-ui-profile-authority-remove-button.tsx | 8 +- ...otocol-ui-profile-avatar-update-button.tsx | 12 +- ...rotocol-ui-profile-button-add-identity.tsx | 12 +- ...ocol-ui-profile-button-remove-identity.tsx | 16 +- ...y-protocol-ui-profile-card-authorities.tsx | 6 +- ...ey-protocol-ui-profile-card-identities.tsx | 42 +- .../ui/pubkey-protocol-ui-profile-card.tsx | 28 +- ...i-profile-profile-authority-add-button.tsx | 6 +- .../ui/pubkey-protocol-ui-profile.tsx | 7 +- .../data-access/pubkey-protocol-provider.tsx | 2 - 57 files changed, 1158 insertions(+), 923 deletions(-) delete mode 100644 anchor/programs/pubkey-protocol/src/instructions/identity/mod.rs rename anchor/programs/pubkey-protocol/src/instructions/{identity/add.rs => profile/profile_identity_add.rs} (81%) rename anchor/programs/pubkey-protocol/src/instructions/{identity/remove.rs => profile/profile_identity_remove.rs} (78%) rename anchor/programs/pubkey-protocol/src/instructions/{community/verify_profile_identity.rs => profile/profile_identity_verify.rs} (89%) delete mode 100644 anchor/tests/pubkey-protocol-identity.spec.ts delete mode 100644 cli/src/utils/get-fee-payer-keypair.ts create mode 100644 web/src/app/features/pubkey-community/data-access/pubkey-community-provider.tsx create mode 100644 web/src/app/features/pubkey-community/ui/pubkey-protocol-ui-community-select.tsx diff --git a/anchor/Anchor.toml b/anchor/Anchor.toml index 2c4954b..153c429 100644 --- a/anchor/Anchor.toml +++ b/anchor/Anchor.toml @@ -15,7 +15,7 @@ cluster = "Localnet" wallet = "~/.config/solana/id.json" [scripts] -test = "../node_modules/.bin/nx run anchor:jest" +test = "../node_modules/.bin/nx run anchor:jest --runInBand" [test] startup_wait = 5000 diff --git a/anchor/programs/pubkey-protocol/src/errors.rs b/anchor/programs/pubkey-protocol/src/errors.rs index 0b39614..2d1fb59 100644 --- a/anchor/programs/pubkey-protocol/src/errors.rs +++ b/anchor/programs/pubkey-protocol/src/errors.rs @@ -6,8 +6,6 @@ pub enum ProtocolError { CannotRemoveLastAuthority, #[msg("Cannot remove the Solana provider")] CannotRemoveSolanaProvider, - #[msg("Community verification already exists")] - CommunityVerificationAlreadyExists, #[msg("Account not owned by program")] InvalidAccountOwner, #[msg("Account too large")] @@ -86,4 +84,6 @@ pub enum ProtocolError { UnauthorizedCommunityAction, #[msg("Account is not defined in config.community_authority")] UnAuthorizedCommunityAuthority, + #[msg("Account is not a signer for this community")] + UnAuthorizedCommunitySigner, } diff --git a/anchor/programs/pubkey-protocol/src/instructions/community/mod.rs b/anchor/programs/pubkey-protocol/src/instructions/community/mod.rs index 83b71da..0a06dfc 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/community/mod.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/community/mod.rs @@ -7,7 +7,6 @@ pub mod community_update_authority_approve; pub mod community_update_authority_decline; pub mod community_update_authority_request; pub mod community_update_details; -pub mod verify_profile_identity; pub use community_create::*; pub use community_provider_disable::*; @@ -18,4 +17,3 @@ pub use community_update_authority_approve::*; pub use community_update_authority_decline::*; pub use community_update_authority_request::*; pub use community_update_details::*; -pub use verify_profile_identity::*; diff --git a/anchor/programs/pubkey-protocol/src/instructions/identity/mod.rs b/anchor/programs/pubkey-protocol/src/instructions/identity/mod.rs deleted file mode 100644 index a1c0719..0000000 --- a/anchor/programs/pubkey-protocol/src/instructions/identity/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod add; -pub mod remove; - -pub use add::*; -pub use remove::*; diff --git a/anchor/programs/pubkey-protocol/src/instructions/mod.rs b/anchor/programs/pubkey-protocol/src/instructions/mod.rs index d89cd17..2a81a81 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/mod.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/mod.rs @@ -1,9 +1,7 @@ pub mod community; pub mod config; -pub mod identity; pub mod profile; pub use community::*; pub use config::*; -pub use identity::*; pub use profile::*; diff --git a/anchor/programs/pubkey-protocol/src/instructions/profile/mod.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/mod.rs index bb3b850..a3afd0c 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/profile/mod.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/mod.rs @@ -1,9 +1,15 @@ pub mod profile_authority_add; pub mod profile_authority_remove; pub mod profile_create; +pub mod profile_identity_add; +pub mod profile_identity_remove; +pub mod profile_identity_verify; pub mod profile_update_details; pub use profile_authority_add::*; pub use profile_authority_remove::*; pub use profile_create::*; +pub use profile_identity_add::*; +pub use profile_identity_remove::*; +pub use profile_identity_verify::*; pub use profile_update_details::*; diff --git a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_add.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_add.rs index f86a365..fc8f414 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_add.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_add.rs @@ -15,18 +15,20 @@ pub struct ProfileAuthorityAdd<'info> { &profile.username.as_bytes() ], bump = profile.bump, - has_one = fee_payer @ ProtocolError::UnAuthorized, constraint = profile.check_for_authority(&authority.key()) @ ProtocolError::UnAuthorized )] pub profile: Account<'info, Profile>, pub authority: Signer<'info>, + pub community: Account<'info, Community>, + #[account( mut, - constraint = fee_payer.key().ne(&authority.key()) @ ProtocolError::InvalidFeePayer + constraint = community.check_for_signer(&fee_payer.key()) @ ProtocolError::UnAuthorizedCommunitySigner, )] pub fee_payer: Signer<'info>, + pub system_program: Program<'info, System>, } diff --git a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_remove.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_remove.rs index 0603b99..ebb3b91 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_remove.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_authority_remove.rs @@ -14,16 +14,18 @@ pub struct ProfileAuthorityRemove<'info> { &profile.username.as_bytes() ], bump = profile.bump, - has_one = fee_payer @ ProtocolError::UnAuthorized, + // has_one = fee_payer @ ProtocolError::UnAuthorized, constraint = profile.check_for_authority(&authority.key()) @ ProtocolError::UnAuthorized )] pub profile: Account<'info, Profile>, pub authority: Signer<'info>, + pub community: Account<'info, Community>, + #[account( mut, - constraint = fee_payer.key().ne(&authority.key()) @ ProtocolError::InvalidFeePayer + constraint = community.check_for_signer(&fee_payer.key()) @ ProtocolError::UnAuthorizedCommunitySigner, )] pub fee_payer: Signer<'info>, } diff --git a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_create.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_create.rs index 4e47272..67192b1 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_create.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_create.rs @@ -33,9 +33,11 @@ pub struct ProfileCreate<'info> { pub authority: Signer<'info>, + pub community: Account<'info, Community>, + #[account( mut, - constraint = fee_payer.key().ne(&authority.key()) @ ProtocolError::InvalidFeePayer + constraint = community.check_for_signer(&fee_payer.key()) @ ProtocolError::UnAuthorizedCommunitySigner, )] pub fee_payer: Signer<'info>, @@ -43,9 +45,9 @@ pub struct ProfileCreate<'info> { } pub fn profile_create(ctx: Context, args: ProfileCreateArgs) -> Result<()> { - let profile = &mut ctx.accounts.profile; let authority = ctx.accounts.authority.key(); - let fee_payer = ctx.accounts.fee_payer.key(); + let community = &ctx.accounts.community; + let profile = &mut ctx.accounts.profile; let pointer = &mut ctx.accounts.pointer; // Creating pointer account @@ -69,7 +71,7 @@ pub fn profile_create(ctx: Context, args: ProfileCreateArgs) -> R provider: IdentityProvider::Solana, provider_id: authority.key().to_string(), name: "Primary Wallet".to_owned(), - communities: vec![], + communities: vec![community.key()], }; profile.set_inner(Profile { @@ -77,7 +79,6 @@ pub fn profile_create(ctx: Context, args: ProfileCreateArgs) -> R username, name, avatar_url, - fee_payer, authorities: vec![authority], identities: vec![set_primary_wallet], }); diff --git a/anchor/programs/pubkey-protocol/src/instructions/identity/add.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_add.rs similarity index 81% rename from anchor/programs/pubkey-protocol/src/instructions/identity/add.rs rename to anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_add.rs index 95491a1..3857323 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/identity/add.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_add.rs @@ -6,8 +6,8 @@ use crate::state::*; use crate::utils::*; #[derive(Accounts)] -#[instruction(args: AddIdentityArgs)] -pub struct AddIdentity<'info> { +#[instruction(args: ProfileIdentityAddArgs)] +pub struct ProfileIdentityAdd<'info> { #[account( mut, seeds = [ @@ -16,7 +16,6 @@ pub struct AddIdentity<'info> { &profile.username.as_bytes() ], bump = profile.bump, - has_one = fee_payer @ ProtocolError::UnAuthorized, constraint = profile.check_for_authority(&authority.key()) @ ProtocolError::UnAuthorized )] pub profile: Account<'info, Profile>, @@ -32,18 +31,24 @@ pub struct AddIdentity<'info> { pub authority: Signer<'info>, + pub community: Account<'info, Community>, + #[account( mut, - constraint = fee_payer.key().ne(&authority.key()) @ ProtocolError::InvalidFeePayer + constraint = community.check_for_signer(&fee_payer.key()) @ ProtocolError::UnAuthorizedCommunitySigner, )] pub fee_payer: Signer<'info>, pub system_program: Program<'info, System>, } -pub fn add(ctx: Context, args: AddIdentityArgs) -> Result<()> { - let profile = &mut ctx.accounts.profile; - let pointer = &mut ctx.accounts.pointer; +pub fn profile_identity_add( + ctx: Context, + args: ProfileIdentityAddArgs, +) -> Result<()> { + let community = &ctx.accounts.community; let fee_payer = &ctx.accounts.fee_payer; + let pointer = &mut ctx.accounts.pointer; + let profile = &mut ctx.accounts.profile; let system_program = &ctx.accounts.system_program; // Initializing pointer account @@ -60,7 +65,7 @@ pub fn add(ctx: Context, args: AddIdentityArgs) -> Result<()> { provider: args.provider, provider_id: args.provider_id, name: args.name, - communities: vec![], + communities: vec![community.key()], }; identity.validate()?; @@ -89,7 +94,7 @@ pub fn add(ctx: Context, args: AddIdentityArgs) -> Result<()> { } #[derive(AnchorSerialize, AnchorDeserialize, Debug)] -pub struct AddIdentityArgs { +pub struct ProfileIdentityAddArgs { provider: IdentityProvider, provider_id: String, name: String, diff --git a/anchor/programs/pubkey-protocol/src/instructions/identity/remove.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_remove.rs similarity index 78% rename from anchor/programs/pubkey-protocol/src/instructions/identity/remove.rs rename to anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_remove.rs index 9beea74..e790df1 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/identity/remove.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_remove.rs @@ -5,8 +5,8 @@ use crate::errors::*; use crate::state::*; #[derive(Accounts)] -#[instruction(args: RemoveIdentityArgs)] -pub struct RemoveIdentity<'info> { +#[instruction(args: ProfileIdentityRemoveArgs)] +pub struct ProfileIdentityRemove<'info> { #[account( mut, seeds = [ @@ -15,7 +15,6 @@ pub struct RemoveIdentity<'info> { &profile.username.as_bytes() ], bump = profile.bump, - has_one = fee_payer @ ProtocolError::UnAuthorized, constraint = profile.check_for_authority(&authority.key()) @ ProtocolError::UnAuthorized )] pub profile: Account<'info, Profile>, @@ -31,15 +30,20 @@ pub struct RemoveIdentity<'info> { pub authority: Signer<'info>, + pub community: Account<'info, Community>, + #[account( mut, - constraint = fee_payer.key().ne(&authority.key()) @ ProtocolError::InvalidFeePayer + constraint = community.check_for_signer(&fee_payer.key()) @ ProtocolError::UnAuthorizedCommunitySigner, )] pub fee_payer: Signer<'info>, pub system_program: Program<'info, System>, } -pub fn remove(ctx: Context, args: RemoveIdentityArgs) -> Result<()> { +pub fn profile_identity_remove( + ctx: Context, + args: ProfileIdentityRemoveArgs, +) -> Result<()> { let profile = &mut ctx.accounts.profile; let provider = ctx.accounts.pointer.provider.clone(); @@ -60,6 +64,6 @@ pub fn remove(ctx: Context, args: RemoveIdentityArgs) -> Result< } #[derive(AnchorSerialize, AnchorDeserialize)] -pub struct RemoveIdentityArgs { +pub struct ProfileIdentityRemoveArgs { pub provider_id: String, } diff --git a/anchor/programs/pubkey-protocol/src/instructions/community/verify_profile_identity.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_verify.rs similarity index 89% rename from anchor/programs/pubkey-protocol/src/instructions/community/verify_profile_identity.rs rename to anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_verify.rs index ecd57f0..1c8c112 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/community/verify_profile_identity.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_identity_verify.rs @@ -4,8 +4,8 @@ use crate::errors::*; use crate::state::*; #[derive(Accounts)] -#[instruction(args: VerifyProfileIdentityArgs)] -pub struct VerifyProfileIdentity<'info> { +#[instruction(args: ProfileIdentityVerifyArgs)] +pub struct ProfileIdentityVerify<'info> { #[account(mut)] pub community: Account<'info, Community>, @@ -25,9 +25,9 @@ pub struct VerifyProfileIdentity<'info> { pub fee_payer: Signer<'info>, } -pub fn verify_profile_identity( - ctx: Context, - args: VerifyProfileIdentityArgs, +pub fn profile_identity_verify( + ctx: Context, + args: ProfileIdentityVerifyArgs, ) -> Result<()> { let community = &ctx.accounts.community; let profile = &mut ctx.accounts.profile; @@ -68,7 +68,7 @@ pub fn verify_profile_identity( } #[derive(AnchorSerialize, AnchorDeserialize, Debug)] -pub struct VerifyProfileIdentityArgs { +pub struct ProfileIdentityVerifyArgs { provider: IdentityProvider, provider_id: String, } diff --git a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_update_details.rs b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_update_details.rs index 27f1318..3c85af8 100644 --- a/anchor/programs/pubkey-protocol/src/instructions/profile/profile_update_details.rs +++ b/anchor/programs/pubkey-protocol/src/instructions/profile/profile_update_details.rs @@ -6,8 +6,8 @@ use crate::state::*; use crate::utils::*; #[derive(Accounts)] -#[instruction(args: UpdateProfileDetailsArgs)] -pub struct UpdateProfileDetails<'info> { +#[instruction(args: ProfileUpdateDetailsArgs)] +pub struct ProfileUpdateDetails<'info> { #[account( mut, seeds = [ @@ -16,21 +16,23 @@ pub struct UpdateProfileDetails<'info> { &profile.username.as_bytes() ], bump = profile.bump, - has_one = fee_payer @ ProtocolError::UnAuthorized, - constraint = profile.check_for_authority(&args.authority) @ ProtocolError::UnAuthorized + constraint = profile.check_for_authority(&authority.key()) @ ProtocolError::UnAuthorized )] pub profile: Account<'info, Profile>, + pub authority: Signer<'info>, + pub community: Account<'info, Community>, + #[account( mut, - constraint = fee_payer.key().ne(&args.authority) @ ProtocolError::InvalidFeePayer + constraint = community.check_for_signer(&fee_payer.key()) @ ProtocolError::UnAuthorizedCommunitySigner, )] pub fee_payer: Signer<'info>, } pub fn profile_update_details( - ctx: Context, - args: UpdateProfileDetailsArgs, + ctx: Context, + args: ProfileUpdateDetailsArgs, ) -> Result<()> { let profile = &mut ctx.accounts.profile; @@ -53,8 +55,7 @@ pub fn profile_update_details( } #[derive(AnchorSerialize, AnchorDeserialize)] -pub struct UpdateProfileDetailsArgs { - pub authority: Pubkey, +pub struct ProfileUpdateDetailsArgs { pub new_name: Option, pub new_avatar_url: Option, } diff --git a/anchor/programs/pubkey-protocol/src/lib.rs b/anchor/programs/pubkey-protocol/src/lib.rs index 7443396..2ad704a 100644 --- a/anchor/programs/pubkey-protocol/src/lib.rs +++ b/anchor/programs/pubkey-protocol/src/lib.rs @@ -35,19 +35,25 @@ pub mod pubkey_protocol { } pub fn profile_update_details( - ctx: Context, - args: UpdateProfileDetailsArgs, + ctx: Context, + args: ProfileUpdateDetailsArgs, ) -> Result<()> { profile::profile_update_details(ctx, args) } // Identity Actions - pub fn add_identity(ctx: Context, args: AddIdentityArgs) -> Result<()> { - identity::add(ctx, args) + pub fn add_identity( + ctx: Context, + args: ProfileIdentityAddArgs, + ) -> Result<()> { + profile::profile_identity_add(ctx, args) } - pub fn remove_identity(ctx: Context, args: RemoveIdentityArgs) -> Result<()> { - identity::remove(ctx, args) + pub fn remove_identity( + ctx: Context, + args: ProfileIdentityRemoveArgs, + ) -> Result<()> { + profile::profile_identity_remove(ctx, args) } // Community Actions @@ -115,10 +121,10 @@ pub mod pubkey_protocol { config::config_init(ctx, args) } - pub fn verify_profile_identity( - ctx: Context, - args: VerifyProfileIdentityArgs, + pub fn profile_identity_verify( + ctx: Context, + args: ProfileIdentityVerifyArgs, ) -> Result<()> { - community::verify_profile_identity(ctx, args) + profile::profile_identity_verify(ctx, args) } } diff --git a/anchor/programs/pubkey-protocol/src/state/community.rs b/anchor/programs/pubkey-protocol/src/state/community.rs index aa66300..91d3d90 100644 --- a/anchor/programs/pubkey-protocol/src/state/community.rs +++ b/anchor/programs/pubkey-protocol/src/state/community.rs @@ -86,4 +86,18 @@ impl Community { pub fn check_for_authority(&self, authority: &Pubkey) -> bool { &self.authority == authority } + + pub fn check_for_signer(&self, authority: &Pubkey) -> bool { + msg!( + "Checking if signers {} contains {}", + self.signers + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", "), + authority.to_string() + ); + + self.signers.iter().any(|s| s == authority) + } } diff --git a/anchor/programs/pubkey-protocol/src/state/profile.rs b/anchor/programs/pubkey-protocol/src/state/profile.rs index 11de972..16e0fb1 100644 --- a/anchor/programs/pubkey-protocol/src/state/profile.rs +++ b/anchor/programs/pubkey-protocol/src/state/profile.rs @@ -15,20 +15,12 @@ pub struct Profile { pub name: String, // Avatar URL pub avatar_url: String, - // Remote fee payer - pub fee_payer: Pubkey, // Authorities that have been delegated to pub authorities: Vec, // Identities user have added onto pub identities: Vec, } -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)] -pub struct CommunityVerification { - pub community: Pubkey, - pub verified_by: Pubkey, -} - impl Profile { pub fn size(authorities: &[Pubkey], identities: &[Identity]) -> usize { let authorities_size = 4 + // Vector discriminator @@ -37,8 +29,6 @@ impl Profile { let identities_size = 4 + // Vector discriminator identities.iter().map(|identity| Identity::size(identity)).sum::(); - let communities_size = 4 + (0 * std::mem::size_of::()); - 8 + // Anchor discriminator 1 + // bump 4 + MAX_USERNAME_SIZE + @@ -46,8 +36,7 @@ impl Profile { 4 + MAX_URL_SIZE + 32 + // fee_payer authorities_size + - identities_size + - communities_size + identities_size } pub fn validate(&self) -> Result<()> { diff --git a/anchor/src/pubkey-protocol-exports.ts b/anchor/src/pubkey-protocol-exports.ts index dba4b43..2ac83a8 100644 --- a/anchor/src/pubkey-protocol-exports.ts +++ b/anchor/src/pubkey-protocol-exports.ts @@ -130,36 +130,35 @@ export function convertAnchorIdentityProviders(providers: AnchorIdentityProvider } export interface PubKeyProfile { - publicKey: PublicKey authorities: PublicKey[] avatarUrl: string - feePayer?: PublicKey bump?: number identities: PubKeyIdentity[] name: string + publicKey: PublicKey username: string } export interface PubKeyIdentity { + communities?: PublicKey[] name: string provider: IdentityProvider providerId: string - communities?: PublicKey[] } export interface PubKeyPointer { - publicKey: PublicKey - provider: IdentityProvider - providerId: string bump?: number profile: PublicKey + provider: IdentityProvider + providerId: string + publicKey: PublicKey } export interface PubKeyConfig { - publicKey: PublicKey bump: number communityAuthority: PublicKey configAuthority: PublicKey + publicKey: PublicKey } export interface PubKeyCommunity { diff --git a/anchor/target/idl/pubkey_protocol.json b/anchor/target/idl/pubkey_protocol.json index 0739a6a..3ccf905 100644 --- a/anchor/target/idl/pubkey_protocol.json +++ b/anchor/target/idl/pubkey_protocol.json @@ -73,13 +73,13 @@ "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "fee_payer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true }, { "name": "system_program", @@ -91,7 +91,7 @@ "name": "args", "type": { "defined": { - "name": "AddIdentityArgs" + "name": "ProfileIdentityAddArgs" } } } @@ -855,13 +855,13 @@ "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "fee_payer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true }, { "name": "system_program", @@ -941,13 +941,13 @@ "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "fee_payer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true } ], "args": [ @@ -1026,6 +1026,9 @@ "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "fee_payer", "writable": true, @@ -1047,6 +1050,51 @@ } ] }, + { + "name": "profile_identity_verify", + "discriminator": [ + 79, + 14, + 169, + 163, + 144, + 179, + 241, + 71 + ], + "accounts": [ + { + "name": "community", + "writable": true + }, + { + "name": "profile", + "writable": true + }, + { + "name": "pointer" + }, + { + "name": "authority", + "signer": true + }, + { + "name": "fee_payer", + "writable": true, + "signer": true + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": { + "name": "ProfileIdentityVerifyArgs" + } + } + } + ] + }, { "name": "profile_update_details", "discriminator": [ @@ -1105,13 +1153,17 @@ ] } }, + { + "name": "authority", + "signer": true + }, + { + "name": "community" + }, { "name": "fee_payer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true } ], "args": [ @@ -1119,7 +1171,7 @@ "name": "args", "type": { "defined": { - "name": "UpdateProfileDetailsArgs" + "name": "ProfileUpdateDetailsArgs" } } } @@ -1194,13 +1246,13 @@ "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "fee_payer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true }, { "name": "system_program", @@ -1212,52 +1264,7 @@ "name": "args", "type": { "defined": { - "name": "RemoveIdentityArgs" - } - } - } - ] - }, - { - "name": "verify_profile_identity", - "discriminator": [ - 179, - 83, - 99, - 171, - 14, - 91, - 192, - 133 - ], - "accounts": [ - { - "name": "community", - "writable": true - }, - { - "name": "profile", - "writable": true - }, - { - "name": "pointer" - }, - { - "name": "authority", - "signer": true - }, - { - "name": "fee_payer", - "writable": true, - "signer": true - } - ], - "args": [ - { - "name": "args", - "type": { - "defined": { - "name": "VerifyProfileIdentityArgs" + "name": "ProfileIdentityRemoveArgs" } } } @@ -1331,230 +1338,206 @@ }, { "code": 6002, - "name": "CommunityVerificationAlreadyExists", - "msg": "Community verification already exists" - }, - { - "code": 6003, "name": "InvalidAccountOwner", "msg": "Account not owned by program" }, { - "code": 6004, + "code": 6003, "name": "AccountTooLarge", "msg": "Account too large" }, { - "code": 6005, + "code": 6004, "name": "AuthorityAlreadyExists", "msg": "Authority already exists" }, { - "code": 6006, + "code": 6005, "name": "AuthorityNonExistent", "msg": "Authority does not exist" }, { - "code": 6007, + "code": 6006, "name": "InvalidAvatarURL", "msg": "Invalid Avatar Url" }, { - "code": 6008, + "code": 6007, "name": "InvalidCommunityAuthority", "msg": "Invalid Community Authority" }, { - "code": 6009, + "code": 6008, "name": "InvalidDiscordID", "msg": "Invalid Discord ID" }, { - "code": 6010, + "code": 6009, "name": "InvalidDiscordURL", "msg": "Invalid Discord URL" }, { - "code": 6011, + "code": 6010, "name": "InvalidFarcasterID", "msg": "Invalid Farcaster ID" }, { - "code": 6012, + "code": 6011, "name": "InvalidFarcasterURL", "msg": "Invalid Farcaster URL" }, { - "code": 6013, + "code": 6012, "name": "InvalidFeePayer", "msg": "Invalid Fee payer" }, { - "code": 6014, + "code": 6013, "name": "InvalidGitHubID", "msg": "Invalid GitHub ID" }, { - "code": 6015, + "code": 6014, "name": "InvalidGitHubURL", "msg": "Invalid GitHub URL" }, { - "code": 6016, + "code": 6015, "name": "InvalidGoogleID", "msg": "Invalid Google ID" }, { - "code": 6017, + "code": 6016, "name": "InvalidGoogleURL", "msg": "Invalid Google URL" }, { - "code": 6018, + "code": 6017, "name": "InvalidName", "msg": "Invalid Name" }, { - "code": 6019, + "code": 6018, "name": "InvalidProviderIDTooLong", "msg": "Invalid Provider ID (too long)" }, { - "code": 6020, + "code": 6019, "name": "InvalidProviderIDNotFound", "msg": "Invalid Provider ID (not found)" }, { - "code": 6021, + "code": 6020, "name": "InvalidProviderNameTooLong", "msg": "Invalid Provider Name (too long)" }, { - "code": 6022, + "code": 6021, "name": "InvalidSlug", "msg": "Invalid Slug" }, { - "code": 6023, + "code": 6022, "name": "InvalidSolanaPubKey", "msg": "Invalid Solana Public Key" }, { - "code": 6024, + "code": 6023, "name": "InvalidTelegramID", "msg": "Invalid Telegram ID" }, { - "code": 6025, + "code": 6024, "name": "InvalidTelegramURL", "msg": "Invalid Telegram URL" }, { - "code": 6026, + "code": 6025, "name": "InvalidUsername", "msg": "Invalid Username" }, { - "code": 6027, + "code": 6026, "name": "InvalidWebsiteURL", "msg": "Invalid Website URL" }, { - "code": 6028, + "code": 6027, "name": "InvalidXID", "msg": "Invalid X ID" }, { - "code": 6029, + "code": 6028, "name": "InvalidXURL", "msg": "Invalid X URL" }, { - "code": 6030, + "code": 6029, "name": "IdentityProfileInvalid", "msg": "Invalid Identity Profile Authority" }, { - "code": 6031, + "code": 6030, "name": "IdentityAlreadyExists", "msg": "Identity already exists" }, { - "code": 6032, + "code": 6031, "name": "IdentityNonExistent", "msg": "Identity does not exist" }, { - "code": 6033, + "code": 6032, "name": "MaxSizeReached", "msg": "Array reached max size" }, { - "code": 6034, + "code": 6033, "name": "ProviderAlreadyExists", "msg": "Provider already exists" }, { - "code": 6035, + "code": 6034, "name": "ProviderDoesNotExist", "msg": "Provider does not exist" }, { - "code": 6036, + "code": 6035, "name": "SignerAlreadyExists", "msg": "Signer already exists" }, { - "code": 6037, + "code": 6036, "name": "SignerDoesNotExist", "msg": "Signer does not exist" }, { - "code": 6038, + "code": 6037, "name": "SignerRequired", "msg": "At least one signer is required" }, { - "code": 6039, + "code": 6038, "name": "UnAuthorized", "msg": "Account unauthorized to perform this action" }, { - "code": 6040, + "code": 6039, "name": "UnauthorizedCommunityAction", "msg": "Unauthorized community action" }, { - "code": 6041, + "code": 6040, "name": "UnAuthorizedCommunityAuthority", "msg": "Account is not defined in config.community_authority" + }, + { + "code": 6041, + "name": "UnAuthorizedCommunitySigner", + "msg": "Account is not a signer for this community" } ], "types": [ - { - "name": "AddIdentityArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "provider", - "type": { - "defined": { - "name": "IdentityProvider" - } - } - }, - { - "name": "provider_id", - "type": "string" - }, - { - "name": "name", - "type": "string" - } - ] - } - }, { "name": "Community", "type": { @@ -1869,10 +1852,6 @@ "name": "avatar_url", "type": "string" }, - { - "name": "fee_payer", - "type": "pubkey" - }, { "name": "authorities", "type": { @@ -1937,113 +1916,133 @@ } }, { - "name": "RemoveIdentityArgs", + "name": "ProfileIdentityAddArgs", "type": { "kind": "struct", "fields": [ + { + "name": "provider", + "type": { + "defined": { + "name": "IdentityProvider" + } + } + }, { "name": "provider_id", "type": "string" + }, + { + "name": "name", + "type": "string" } ] } }, { - "name": "UpdateCommunityDetailsArgs", + "name": "ProfileIdentityRemoveArgs", "type": { "kind": "struct", "fields": [ { - "name": "avatar_url", + "name": "provider_id", + "type": "string" + } + ] + } + }, + { + "name": "ProfileIdentityVerifyArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "provider", "type": { - "option": "string" + "defined": { + "name": "IdentityProvider" + } } }, { - "name": "discord", + "name": "provider_id", + "type": "string" + } + ] + } + }, + { + "name": "ProfileUpdateDetailsArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "new_name", "type": { "option": "string" } }, { - "name": "farcaster", + "name": "new_avatar_url", "type": { "option": "string" } - }, + } + ] + } + }, + { + "name": "UpdateCommunityDetailsArgs", + "type": { + "kind": "struct", + "fields": [ { - "name": "github", + "name": "avatar_url", "type": { "option": "string" } }, { - "name": "name", + "name": "discord", "type": { "option": "string" } }, { - "name": "telegram", + "name": "farcaster", "type": { "option": "string" } }, { - "name": "website", + "name": "github", "type": { "option": "string" } }, { - "name": "x", + "name": "name", "type": { "option": "string" } - } - ] - } - }, - { - "name": "UpdateProfileDetailsArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" }, { - "name": "new_name", + "name": "telegram", "type": { "option": "string" } }, { - "name": "new_avatar_url", + "name": "website", "type": { "option": "string" } - } - ] - } - }, - { - "name": "VerifyProfileIdentityArgs", - "type": { - "kind": "struct", - "fields": [ + }, { - "name": "provider", + "name": "x", "type": { - "defined": { - "name": "IdentityProvider" - } + "option": "string" } - }, - { - "name": "provider_id", - "type": "string" } ] } diff --git a/anchor/target/types/pubkey_protocol.ts b/anchor/target/types/pubkey_protocol.ts index 29a6ae4..2f3cf27 100644 --- a/anchor/target/types/pubkey_protocol.ts +++ b/anchor/target/types/pubkey_protocol.ts @@ -79,13 +79,13 @@ export type PubkeyProtocol = { "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "feePayer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true }, { "name": "systemProgram", @@ -97,7 +97,7 @@ export type PubkeyProtocol = { "name": "args", "type": { "defined": { - "name": "addIdentityArgs" + "name": "profileIdentityAddArgs" } } } @@ -861,13 +861,13 @@ export type PubkeyProtocol = { "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "feePayer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true }, { "name": "systemProgram", @@ -947,13 +947,13 @@ export type PubkeyProtocol = { "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "feePayer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true } ], "args": [ @@ -1032,6 +1032,9 @@ export type PubkeyProtocol = { "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "feePayer", "writable": true, @@ -1053,6 +1056,51 @@ export type PubkeyProtocol = { } ] }, + { + "name": "profileIdentityVerify", + "discriminator": [ + 79, + 14, + 169, + 163, + 144, + 179, + 241, + 71 + ], + "accounts": [ + { + "name": "community", + "writable": true + }, + { + "name": "profile", + "writable": true + }, + { + "name": "pointer" + }, + { + "name": "authority", + "signer": true + }, + { + "name": "feePayer", + "writable": true, + "signer": true + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": { + "name": "profileIdentityVerifyArgs" + } + } + } + ] + }, { "name": "profileUpdateDetails", "discriminator": [ @@ -1111,13 +1159,17 @@ export type PubkeyProtocol = { ] } }, + { + "name": "authority", + "signer": true + }, + { + "name": "community" + }, { "name": "feePayer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true } ], "args": [ @@ -1125,7 +1177,7 @@ export type PubkeyProtocol = { "name": "args", "type": { "defined": { - "name": "updateProfileDetailsArgs" + "name": "profileUpdateDetailsArgs" } } } @@ -1200,13 +1252,13 @@ export type PubkeyProtocol = { "name": "authority", "signer": true }, + { + "name": "community" + }, { "name": "feePayer", "writable": true, - "signer": true, - "relations": [ - "profile" - ] + "signer": true }, { "name": "systemProgram", @@ -1218,52 +1270,7 @@ export type PubkeyProtocol = { "name": "args", "type": { "defined": { - "name": "removeIdentityArgs" - } - } - } - ] - }, - { - "name": "verifyProfileIdentity", - "discriminator": [ - 179, - 83, - 99, - 171, - 14, - 91, - 192, - 133 - ], - "accounts": [ - { - "name": "community", - "writable": true - }, - { - "name": "profile", - "writable": true - }, - { - "name": "pointer" - }, - { - "name": "authority", - "signer": true - }, - { - "name": "feePayer", - "writable": true, - "signer": true - } - ], - "args": [ - { - "name": "args", - "type": { - "defined": { - "name": "verifyProfileIdentityArgs" + "name": "profileIdentityRemoveArgs" } } } @@ -1337,230 +1344,206 @@ export type PubkeyProtocol = { }, { "code": 6002, - "name": "communityVerificationAlreadyExists", - "msg": "Community verification already exists" - }, - { - "code": 6003, "name": "invalidAccountOwner", "msg": "Account not owned by program" }, { - "code": 6004, + "code": 6003, "name": "accountTooLarge", "msg": "Account too large" }, { - "code": 6005, + "code": 6004, "name": "authorityAlreadyExists", "msg": "Authority already exists" }, { - "code": 6006, + "code": 6005, "name": "authorityNonExistent", "msg": "Authority does not exist" }, { - "code": 6007, + "code": 6006, "name": "invalidAvatarUrl", "msg": "Invalid Avatar Url" }, { - "code": 6008, + "code": 6007, "name": "invalidCommunityAuthority", "msg": "Invalid Community Authority" }, { - "code": 6009, + "code": 6008, "name": "invalidDiscordId", "msg": "Invalid Discord ID" }, { - "code": 6010, + "code": 6009, "name": "invalidDiscordUrl", "msg": "Invalid Discord URL" }, { - "code": 6011, + "code": 6010, "name": "invalidFarcasterId", "msg": "Invalid Farcaster ID" }, { - "code": 6012, + "code": 6011, "name": "invalidFarcasterUrl", "msg": "Invalid Farcaster URL" }, { - "code": 6013, + "code": 6012, "name": "invalidFeePayer", "msg": "Invalid Fee payer" }, { - "code": 6014, + "code": 6013, "name": "invalidGitHubId", "msg": "Invalid GitHub ID" }, { - "code": 6015, + "code": 6014, "name": "invalidGitHubUrl", "msg": "Invalid GitHub URL" }, { - "code": 6016, + "code": 6015, "name": "invalidGoogleId", "msg": "Invalid Google ID" }, { - "code": 6017, + "code": 6016, "name": "invalidGoogleUrl", "msg": "Invalid Google URL" }, { - "code": 6018, + "code": 6017, "name": "invalidName", "msg": "Invalid Name" }, { - "code": 6019, + "code": 6018, "name": "invalidProviderIdTooLong", "msg": "Invalid Provider ID (too long)" }, { - "code": 6020, + "code": 6019, "name": "invalidProviderIdNotFound", "msg": "Invalid Provider ID (not found)" }, { - "code": 6021, + "code": 6020, "name": "invalidProviderNameTooLong", "msg": "Invalid Provider Name (too long)" }, { - "code": 6022, + "code": 6021, "name": "invalidSlug", "msg": "Invalid Slug" }, { - "code": 6023, + "code": 6022, "name": "invalidSolanaPubKey", "msg": "Invalid Solana Public Key" }, { - "code": 6024, + "code": 6023, "name": "invalidTelegramId", "msg": "Invalid Telegram ID" }, { - "code": 6025, + "code": 6024, "name": "invalidTelegramUrl", "msg": "Invalid Telegram URL" }, { - "code": 6026, + "code": 6025, "name": "invalidUsername", "msg": "Invalid Username" }, { - "code": 6027, + "code": 6026, "name": "invalidWebsiteUrl", "msg": "Invalid Website URL" }, { - "code": 6028, + "code": 6027, "name": "invalidXid", "msg": "Invalid X ID" }, { - "code": 6029, + "code": 6028, "name": "invalidXurl", "msg": "Invalid X URL" }, { - "code": 6030, + "code": 6029, "name": "identityProfileInvalid", "msg": "Invalid Identity Profile Authority" }, { - "code": 6031, + "code": 6030, "name": "identityAlreadyExists", "msg": "Identity already exists" }, { - "code": 6032, + "code": 6031, "name": "identityNonExistent", "msg": "Identity does not exist" }, { - "code": 6033, + "code": 6032, "name": "maxSizeReached", "msg": "Array reached max size" }, { - "code": 6034, + "code": 6033, "name": "providerAlreadyExists", "msg": "Provider already exists" }, { - "code": 6035, + "code": 6034, "name": "providerDoesNotExist", "msg": "Provider does not exist" }, { - "code": 6036, + "code": 6035, "name": "signerAlreadyExists", "msg": "Signer already exists" }, { - "code": 6037, + "code": 6036, "name": "signerDoesNotExist", "msg": "Signer does not exist" }, { - "code": 6038, + "code": 6037, "name": "signerRequired", "msg": "At least one signer is required" }, { - "code": 6039, + "code": 6038, "name": "unAuthorized", "msg": "Account unauthorized to perform this action" }, { - "code": 6040, + "code": 6039, "name": "unauthorizedCommunityAction", "msg": "Unauthorized community action" }, { - "code": 6041, + "code": 6040, "name": "unAuthorizedCommunityAuthority", "msg": "Account is not defined in config.community_authority" + }, + { + "code": 6041, + "name": "unAuthorizedCommunitySigner", + "msg": "Account is not a signer for this community" } ], "types": [ - { - "name": "addIdentityArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "provider", - "type": { - "defined": { - "name": "identityProvider" - } - } - }, - { - "name": "providerId", - "type": "string" - }, - { - "name": "name", - "type": "string" - } - ] - } - }, { "name": "community", "type": { @@ -1875,10 +1858,6 @@ export type PubkeyProtocol = { "name": "avatarUrl", "type": "string" }, - { - "name": "feePayer", - "type": "pubkey" - }, { "name": "authorities", "type": { @@ -1943,113 +1922,133 @@ export type PubkeyProtocol = { } }, { - "name": "removeIdentityArgs", + "name": "profileIdentityAddArgs", "type": { "kind": "struct", "fields": [ + { + "name": "provider", + "type": { + "defined": { + "name": "identityProvider" + } + } + }, { "name": "providerId", "type": "string" + }, + { + "name": "name", + "type": "string" } ] } }, { - "name": "updateCommunityDetailsArgs", + "name": "profileIdentityRemoveArgs", "type": { "kind": "struct", "fields": [ { - "name": "avatarUrl", + "name": "providerId", + "type": "string" + } + ] + } + }, + { + "name": "profileIdentityVerifyArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "provider", "type": { - "option": "string" + "defined": { + "name": "identityProvider" + } } }, { - "name": "discord", + "name": "providerId", + "type": "string" + } + ] + } + }, + { + "name": "profileUpdateDetailsArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "newName", "type": { "option": "string" } }, { - "name": "farcaster", + "name": "newAvatarUrl", "type": { "option": "string" } - }, + } + ] + } + }, + { + "name": "updateCommunityDetailsArgs", + "type": { + "kind": "struct", + "fields": [ { - "name": "github", + "name": "avatarUrl", "type": { "option": "string" } }, { - "name": "name", + "name": "discord", "type": { "option": "string" } }, { - "name": "telegram", + "name": "farcaster", "type": { "option": "string" } }, { - "name": "website", + "name": "github", "type": { "option": "string" } }, { - "name": "x", + "name": "name", "type": { "option": "string" } - } - ] - } - }, - { - "name": "updateProfileDetailsArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" }, { - "name": "newName", + "name": "telegram", "type": { "option": "string" } }, { - "name": "newAvatarUrl", + "name": "website", "type": { "option": "string" } - } - ] - } - }, - { - "name": "verifyProfileIdentityArgs", - "type": { - "kind": "struct", - "fields": [ + }, { - "name": "provider", + "name": "x", "type": { - "defined": { - "name": "identityProvider" - } + "option": "string" } - }, - { - "name": "providerId", - "type": "string" } ] } diff --git a/anchor/tests/pubkey-protocol-community.spec.ts b/anchor/tests/pubkey-protocol-community.spec.ts index 0c00cd6..b5ed20c 100644 --- a/anchor/tests/pubkey-protocol-community.spec.ts +++ b/anchor/tests/pubkey-protocol-community.spec.ts @@ -33,11 +33,11 @@ fdescribe('pubkey-protocol-community', () => { beforeEach(async () => { communitySlug = unique('acme') community = await createTestCommunity({ - slug: communitySlug, - program, authority: communityAuthority, - wallet: feePayer, + communityAuthority: feePayer.publicKey, config, + program, + slug: communitySlug, }) }) @@ -410,7 +410,7 @@ fdescribe('pubkey-protocol-community', () => { } catch (error) { // console.log('error', error) expect(error.error.errorCode.code).toEqual('UnAuthorizedCommunityAuthority') - expect(error.error.errorCode.number).toEqual(6041) + expect(error.error.errorCode.number).toEqual(6040) expect(error.error.errorMessage).toEqual('Account is not defined in config.community_authority') } }) @@ -437,7 +437,7 @@ fdescribe('pubkey-protocol-community', () => { expect(true).toBe(false) } catch (error) { expect(error.error.errorCode.code).toBe('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) } }) }) @@ -456,7 +456,7 @@ fdescribe('pubkey-protocol-community', () => { expect(true).toBe(false) } catch (error) { expect(error.error.errorCode.code).toBe('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) } }) @@ -473,7 +473,7 @@ fdescribe('pubkey-protocol-community', () => { expect(true).toBe(false) } catch (error) { expect(error.error.errorCode.code).toBe('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) } }) @@ -492,7 +492,7 @@ fdescribe('pubkey-protocol-community', () => { expect(true).toBe(false) } catch (error) { expect(error.error.errorCode.code).toBe('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) } }) }) @@ -514,7 +514,7 @@ fdescribe('pubkey-protocol-community', () => { expect(true).toBe(false) } catch (error) { expect(error.error.errorCode.code).toBe('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) } }) @@ -534,7 +534,7 @@ fdescribe('pubkey-protocol-community', () => { expect(true).toBe(false) } catch (error) { expect(error.error.errorCode.code).toBe('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) } }) }) @@ -561,7 +561,7 @@ fdescribe('pubkey-protocol-community', () => { } catch (error) { // Check that the error is related to unauthorized action expect(error.error.errorCode.code).toEqual('UnAuthorized') - expect(error.error.errorCode.number).toEqual(6039) + expect(error.error.errorCode.number).toEqual(6038) expect(error.error.errorMessage).toEqual('Account unauthorized to perform this action') } diff --git a/anchor/tests/pubkey-protocol-identity.spec.ts b/anchor/tests/pubkey-protocol-identity.spec.ts deleted file mode 100644 index 2d4dc13..0000000 --- a/anchor/tests/pubkey-protocol-identity.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -import * as anchor from '@coral-xyz/anchor' -import { Keypair, LAMPORTS_PER_SOL, SystemProgram } from '@solana/web3.js' -import { convertToAnchorIdentityProvider, getPubKeyPointerPda, IdentityProvider, PubkeyProtocol } from '../src' -import { - airdropAccounts, - createOrGetTestConfig, - createTestCommunity, - createTestProfile, - getTestPdaPointer, - unique, -} from './utils' - -describe('pubkey-protocol-identity', () => { - const provider = anchor.AnchorProvider.env() - anchor.setProvider(provider) - const feePayer = provider.wallet as anchor.Wallet - const program = anchor.workspace.PubkeyProtocol as anchor.Program - - const username = unique('harkl') - const slug = unique('pubkey') - const communityAuthority = Keypair.generate() - const profileOwner = Keypair.generate() - let community: anchor.web3.PublicKey - let profile: anchor.web3.PublicKey - let config: anchor.web3.PublicKey - - const discordIdentity = { - provider: IdentityProvider.Discord, - providerId: Date.now().toString(), - name: `${username}123`, - } - - const profilePda = getTestPdaPointer({ - programId: program.programId, - provider: IdentityProvider.Discord, - providerId: discordIdentity.providerId, - }) - - beforeAll(async () => { - const res = await createOrGetTestConfig(program, feePayer.publicKey) - config = res.config - - await airdropAccounts(provider, [ - { label: 'communityAuthority', publicKey: communityAuthority.publicKey }, - { label: 'profileOwner', publicKey: profileOwner.publicKey }, - ]) - community = await createTestCommunity({ slug, program, authority: communityAuthority, wallet: feePayer, config }) - profile = await createTestProfile(username, program, profileOwner, feePayer.publicKey) - }) - - it('should add an identity', async () => { - await program.methods - .addIdentity({ - provider: convertToAnchorIdentityProvider(IdentityProvider.Discord), - providerId: discordIdentity.providerId, - name: `${username}123`, - }) - .accountsStrict({ - authority: profileOwner.publicKey, - feePayer: feePayer.publicKey, - pointer: profilePda.publicKey, - profile, - systemProgram: SystemProgram.programId, - }) - .signers([profileOwner]) - .rpc() - - const { identities } = await program.account.profile.fetch(profile) - const pointerData = await program.account.pointer.fetch(profilePda.publicKey) - - const postBalance = await provider.connection.getBalance(profileOwner.publicKey) - - expect(postBalance).toStrictEqual(LAMPORTS_PER_SOL) - expect(identities).toEqual([ - { - provider: convertToAnchorIdentityProvider(IdentityProvider.Solana), - providerId: profileOwner.publicKey.toBase58(), - name: 'Primary Wallet', - communities: [], - }, - { - provider: convertToAnchorIdentityProvider(discordIdentity.provider), - providerId: discordIdentity.providerId, - name: discordIdentity.name, - communities: [], - }, - ]) - - expect(pointerData.bump).toStrictEqual(profilePda.bump) - expect(pointerData.providerId).toStrictEqual(discordIdentity.providerId) - expect(pointerData.provider).toStrictEqual(convertToAnchorIdentityProvider(discordIdentity.provider)) - expect(pointerData.profile).toStrictEqual(profile) - }) - - it('should remove an identity', async () => { - await program.methods - .removeIdentity({ - providerId: discordIdentity.providerId, - }) - .accountsStrict({ - authority: profileOwner.publicKey, - feePayer: feePayer.publicKey, - pointer: profilePda.publicKey, - profile, - systemProgram: SystemProgram.programId, - }) - .signers([profileOwner]) - .rpc() - - const { identities } = await program.account.profile.fetch(profile) - const pointerData = await program.account.pointer.fetchNullable(profilePda.publicKey) - - expect(identities).toEqual([ - { - provider: convertToAnchorIdentityProvider(IdentityProvider.Solana), - providerId: profileOwner.publicKey.toString(), - name: 'Primary Wallet', - communities: [], - }, - ]) - expect(pointerData).toBeNull() - }) - - it('should verify an identity', async () => { - const [pointer] = getPubKeyPointerPda({ - programId: program.programId, - provider: IdentityProvider.Solana, - providerId: profileOwner.publicKey.toString(), - }) - await program.methods - .verifyProfileIdentity({ - provider: convertToAnchorIdentityProvider(IdentityProvider.Solana), - providerId: profileOwner.publicKey.toString(), - }) - .accountsStrict({ - community, - profile, - pointer, - authority: communityAuthority.publicKey, - feePayer: feePayer.publicKey, - }) - .signers([communityAuthority]) - .rpc() - - // Fetch and check the Profile account - const profileAccount = await program.account.profile.fetch(profile) - - const identityVerification = profileAccount.identities.find( - (i) => i.providerId === profileOwner.publicKey.toString(), - ) - expect(identityVerification).toBeDefined() - if (identityVerification) { - expect(identityVerification.communities).toContainEqual(community) - } - - // Fetch and check the Community account - const communityAccount = await program.account.community.fetch(community) - expect(communityAccount.providers).toContainEqual(convertToAnchorIdentityProvider(IdentityProvider.Solana)) - }) -}) diff --git a/anchor/tests/pubkey-protocol-profile.spec.ts b/anchor/tests/pubkey-protocol-profile.spec.ts index 58bb97e..d4435c4 100644 --- a/anchor/tests/pubkey-protocol-profile.spec.ts +++ b/anchor/tests/pubkey-protocol-profile.spec.ts @@ -1,9 +1,17 @@ import * as anchor from '@coral-xyz/anchor' import { Program } from '@coral-xyz/anchor' import { Keypair, LAMPORTS_PER_SOL, SystemProgram } from '@solana/web3.js' -import { getPubKeyPointerPda, getPubKeyProfilePda, IdentityProvider } from '../src' +import { convertToAnchorIdentityProvider, getPubKeyPointerPda, getPubKeyProfilePda, IdentityProvider } from '../src' import { PubkeyProtocol } from '../target/types/pubkey_protocol' -import { airdropAccounts, createTestProfile, getProfileAvatarUrl, unique } from './utils' +import { + airdropAccounts, + createOrGetTestConfig, + createTestCommunity, + createTestProfile, + getProfileAvatarUrl, + getTestPdaPointer, + unique, +} from './utils' describe('pubkey-protocol-profile', () => { const username = unique('alice') @@ -12,116 +20,275 @@ describe('pubkey-protocol-profile', () => { anchor.setProvider(provider) const feePayer = provider.wallet as anchor.Wallet const program = anchor.workspace.PubkeyProtocol as Program + const communityAuthority = Keypair.generate() + const aliceWalletOne = Keypair.generate() + const aliceWalletTwo = Keypair.generate() - const communityMember1 = Keypair.generate() - const communityMember2 = Keypair.generate() + let config: anchor.web3.PublicKey + let community: anchor.web3.PublicKey + let communitySlug: string beforeAll(async () => { + const res = await createOrGetTestConfig(program, feePayer.publicKey) + config = res.config + await airdropAccounts(provider, [ - { label: 'communityMember1', publicKey: communityMember1.publicKey }, - { label: 'communityMember2', publicKey: communityMember2.publicKey }, + { label: 'communityAuthority', publicKey: communityAuthority.publicKey }, + { label: 'aliceWalletOne', publicKey: aliceWalletOne.publicKey }, + { label: 'aliceWalletTwo', publicKey: aliceWalletTwo.publicKey }, ]) + communitySlug = unique('acme') + community = await createTestCommunity({ + authority: communityAuthority, + communityAuthority: feePayer.publicKey, + config, + program, + slug: communitySlug, + }) }) - describe('Create and update', () => { - it('should create a profile', async () => { - const [profile, bump] = getPubKeyProfilePda({ username, programId: program.programId }) - const [pointer, bumpPointer] = getPubKeyPointerPda({ - programId: program.programId, - provider: IdentityProvider.Solana, - providerId: communityMember1.publicKey.toString(), - }) - await createTestProfile(username, program, communityMember1, feePayer.publicKey) - - const { - authorities, - identities, - avatarUrl, - bump: receivedBump, - feePayer: receivedFeePayer, - username: receivedUsername, - } = await program.account.profile.fetch(profile) - - const pointerData = await program.account.pointer.fetch(pointer) - const postBalance = await provider.connection.getBalance(communityMember1.publicKey) - - expect(postBalance).toStrictEqual(LAMPORTS_PER_SOL) - expect(receivedBump).toStrictEqual(bump) - expect(receivedUsername).toStrictEqual(username) - expect(avatarUrl).toStrictEqual(getProfileAvatarUrl(username)) - expect(authorities).toEqual([communityMember1.publicKey]) - expect(receivedFeePayer).toStrictEqual(feePayer.publicKey) - - expect(identities).toEqual([ - { - provider: { solana: {} }, - providerId: communityMember1.publicKey.toString(), - name: 'Primary Wallet', - communities: [], - }, - ]) - expect(pointerData.bump).toStrictEqual(bumpPointer) - expect(pointerData.providerId).toStrictEqual(communityMember1.publicKey.toString()) - expect(pointerData.provider).toStrictEqual({ solana: {} }) - expect(pointerData.profile).toStrictEqual(profile) - }) + describe('authorized', () => { + describe('Create and update', () => { + it('should create a profile', async () => { + const [profile, bump] = getPubKeyProfilePda({ username, programId: program.programId }) + const [pointer, bumpPointer] = getPubKeyPointerPda({ + programId: program.programId, + provider: IdentityProvider.Solana, + providerId: aliceWalletOne.publicKey.toString(), + }) - it('should update profile details', async () => { - const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) - const input = { - authority: communityMember1.publicKey, - newName: 'Test Profile', - newAvatarUrl: getProfileAvatarUrl(`${username}_new`), - } - await program.methods - .profileUpdateDetails(input) - .accounts({ - profile, - feePayer: feePayer.publicKey, + await createTestProfile({ + community, + communityAuthority, + username, + program, + profileOwner: aliceWalletOne, }) - .rpc() - const { avatarUrl: newAvatarUrl, name: newName } = await program.account.profile.fetch(profile) - expect(newAvatarUrl).toStrictEqual(input.newAvatarUrl) - expect(newName).toStrictEqual(input.newName) + const { + authorities, + identities, + avatarUrl, + bump: receivedBump, + username: receivedUsername, + } = await program.account.profile.fetch(profile) + + const pointerData = await program.account.pointer.fetch(pointer) + const postBalance = await provider.connection.getBalance(aliceWalletOne.publicKey) + + expect(postBalance).toStrictEqual(LAMPORTS_PER_SOL) + expect(receivedBump).toStrictEqual(bump) + expect(receivedUsername).toStrictEqual(username) + expect(avatarUrl).toStrictEqual(getProfileAvatarUrl(username)) + expect(authorities).toEqual([aliceWalletOne.publicKey]) + + expect(identities[0].provider).toStrictEqual({ solana: {} }) + expect(identities[0].providerId).toStrictEqual(aliceWalletOne.publicKey.toString()) + expect(identities[0].name).toStrictEqual('Primary Wallet') + expect(identities[0].communities.map((c) => c.toString())).toStrictEqual([community.toString()]) + + expect(pointerData.bump).toStrictEqual(bumpPointer) + expect(pointerData.providerId).toStrictEqual(aliceWalletOne.publicKey.toString()) + expect(pointerData.provider).toStrictEqual({ solana: {} }) + expect(pointerData.profile).toStrictEqual(profile) + }) + + it('should update profile details', async () => { + const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) + const input = { + authority: aliceWalletOne.publicKey, + newName: 'Test Profile', + newAvatarUrl: getProfileAvatarUrl(`${username}_new`), + } + await program.methods + .profileUpdateDetails(input) + .accountsStrict({ + authority: aliceWalletOne.publicKey, + community, + feePayer: communityAuthority.publicKey, + profile, + }) + .signers([communityAuthority, aliceWalletOne]) + .rpc() + + const { avatarUrl: newAvatarUrl, name: newName } = await program.account.profile.fetch(profile) + expect(newAvatarUrl).toStrictEqual(input.newAvatarUrl) + expect(newName).toStrictEqual(input.newName) + }) }) - }) + describe('Authorities', () => { + it('should add an authority', async () => { + const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) - describe('Authorities', () => { - it('should add an authority', async () => { - const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) - - await program.methods - .profileAuthorityAdd({ newAuthority: communityMember2.publicKey }) - .accountsStrict({ - profile, - authority: communityMember1.publicKey, - feePayer: feePayer.publicKey, - systemProgram: SystemProgram.programId, - }) - .signers([communityMember1]) - .rpc() + await program.methods + .profileAuthorityAdd({ newAuthority: aliceWalletTwo.publicKey }) + .accountsStrict({ + profile, + authority: aliceWalletOne.publicKey, + community: community, + feePayer: communityAuthority.publicKey, + systemProgram: SystemProgram.programId, + }) + .signers([communityAuthority, aliceWalletOne]) + .rpc() + + const { authorities } = await program.account.profile.fetch(profile) - const { authorities } = await program.account.profile.fetch(profile) + const postBalance = await provider.connection.getBalance(aliceWalletOne.publicKey) - const postBalance = await provider.connection.getBalance(communityMember1.publicKey) + expect(postBalance).toStrictEqual(LAMPORTS_PER_SOL) + expect(authorities.length).toStrictEqual(2) + }) + + it('should remove an authority', async () => { + const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) + + await program.methods + .profileAuthorityRemove({ authorityToRemove: aliceWalletTwo.publicKey }) + .accountsStrict({ + profile, + authority: aliceWalletOne.publicKey, + feePayer: communityAuthority.publicKey, + community, + }) + .signers([communityAuthority, aliceWalletOne]) + .rpc() - expect(postBalance).toStrictEqual(LAMPORTS_PER_SOL) - expect(authorities.length).toStrictEqual(2) + const { authorities } = await program.account.profile.fetch(profile) + + expect(authorities).toEqual([aliceWalletOne.publicKey]) + }) }) - it('should remove an authority', async () => { - const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) + describe('Identities', () => { + // const username = unique('alice') - await program.methods - .profileAuthorityRemove({ authorityToRemove: communityMember2.publicKey }) - .accountsStrict({ profile, authority: communityMember1.publicKey, feePayer: feePayer.publicKey }) - .signers([communityMember1]) - .rpc() + const discordIdentity = { + provider: IdentityProvider.Discord, + providerId: Date.now().toString(), + name: `${username}123`, + } - const { authorities } = await program.account.profile.fetch(profile) + const profilePda = getTestPdaPointer({ + programId: program.programId, + provider: IdentityProvider.Discord, + providerId: discordIdentity.providerId, + }) + + it('should add an identity', async () => { + const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) + + await program.methods + .addIdentity({ + provider: convertToAnchorIdentityProvider(IdentityProvider.Discord), + providerId: discordIdentity.providerId, + name: `${username}123`, + }) + .accountsStrict({ + community, + authority: aliceWalletOne.publicKey, + feePayer: communityAuthority.publicKey, + pointer: profilePda.publicKey, + profile, + systemProgram: SystemProgram.programId, + }) + .signers([communityAuthority, aliceWalletOne]) + .rpc() + + const { identities } = await program.account.profile.fetch(profile) + const pointerData = await program.account.pointer.fetch(profilePda.publicKey) + + const postBalance = await provider.connection.getBalance(aliceWalletOne.publicKey) + + expect(postBalance).toStrictEqual(LAMPORTS_PER_SOL) + + expect(identities.length).toStrictEqual(2) + + expect(identities[0].provider).toStrictEqual(convertToAnchorIdentityProvider(IdentityProvider.Solana)) + expect(identities[0].providerId).toStrictEqual(aliceWalletOne.publicKey.toString()) + expect(identities[0].name).toStrictEqual('Primary Wallet') + expect(identities[0].communities.map((c) => c.toString())).toStrictEqual([community.toString()]) + + expect(identities[1].provider).toStrictEqual(convertToAnchorIdentityProvider(discordIdentity.provider)) + expect(identities[1].providerId).toStrictEqual(discordIdentity.providerId) + expect(identities[1].name).toStrictEqual(discordIdentity.name) + expect(identities[1].communities.map((c) => c.toString())).toStrictEqual([community.toString()]) + + expect(pointerData.bump).toStrictEqual(profilePda.bump) + expect(pointerData.providerId).toStrictEqual(discordIdentity.providerId) + expect(pointerData.provider).toStrictEqual(convertToAnchorIdentityProvider(discordIdentity.provider)) + expect(pointerData.profile).toStrictEqual(profile) + }) - expect(authorities).toEqual([communityMember1.publicKey]) + it('should remove an identity', async () => { + const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) + await program.methods + .removeIdentity({ + providerId: discordIdentity.providerId, + }) + .accountsStrict({ + community, + authority: aliceWalletOne.publicKey, + feePayer: communityAuthority.publicKey, + pointer: profilePda.publicKey, + profile, + systemProgram: SystemProgram.programId, + }) + .signers([communityAuthority, aliceWalletOne]) + .rpc() + + const { identities } = await program.account.profile.fetch(profile) + const pointerData = await program.account.pointer.fetchNullable(profilePda.publicKey) + + expect(identities.length).toStrictEqual(1) + expect(identities[0].provider).toStrictEqual(convertToAnchorIdentityProvider(IdentityProvider.Solana)) + expect(identities[0].providerId).toStrictEqual(aliceWalletOne.publicKey.toString()) + expect(identities[0].name).toStrictEqual('Primary Wallet') + expect(identities[0].communities.map((c) => c.toString())).toStrictEqual([community.toString()]) + + expect(pointerData).toBeNull() + }) + + it('should verify an identity', async () => { + const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) + const [pointer] = getPubKeyPointerPda({ + programId: program.programId, + provider: IdentityProvider.Solana, + providerId: aliceWalletOne.publicKey.toString(), + }) + await program.methods + .profileIdentityVerify({ + provider: convertToAnchorIdentityProvider(IdentityProvider.Solana), + providerId: aliceWalletOne.publicKey.toString(), + }) + .accountsStrict({ + community, + profile, + pointer, + authority: communityAuthority.publicKey, + feePayer: communityAuthority.publicKey, + }) + .signers([communityAuthority]) + .rpc() + + // Fetch and check the Profile account + const profileAccount = await program.account.profile.fetch(profile) + + const identityVerification = profileAccount.identities.find( + (i) => i.providerId === aliceWalletOne.publicKey.toString(), + ) + expect(identityVerification).toBeDefined() + if (identityVerification) { + expect(identityVerification.communities).toContainEqual(community) + } + + // Fetch and check the Community account + const communityAccount = await program.account.community.fetch(community) + expect(communityAccount.providers).toContainEqual(convertToAnchorIdentityProvider(IdentityProvider.Solana)) + }) }) }) + describe('unauthorized', () => { + // FIXME: Add unauthorized tests + }) }) diff --git a/anchor/tests/utils/create-test-community.ts b/anchor/tests/utils/create-test-community.ts index 08e9abf..542f285 100644 --- a/anchor/tests/utils/create-test-community.ts +++ b/anchor/tests/utils/create-test-community.ts @@ -3,23 +3,23 @@ import { getPubKeyCommunityPda, PubkeyProtocol } from '../../src' import { getCommunityAvatarUrl } from './get-avatar-url' export async function createTestCommunity({ - slug, - program, authority, - wallet, + communityAuthority, config, + program, + slug, }: { - slug: string - program: anchor.Program authority: anchor.web3.Keypair - wallet: anchor.Wallet + communityAuthority: anchor.web3.PublicKey config: anchor.web3.PublicKey + program: anchor.Program + slug: string }) { try { const [community] = getPubKeyCommunityPda({ programId: program.programId, slug }) // console.log( - // `createTestCommunity: community authority ${wallet.publicKey.toString()} created ${slug} with authority ${authority.publicKey.toString()}`, + // `createTestCommunity: community authority ${communityAuthority.toString()} created ${slug} with authority ${authority.publicKey.toString()}`, // ) await program.methods @@ -30,7 +30,7 @@ export async function createTestCommunity({ }) .accountsStrict({ community, - communityAuthority: wallet.publicKey, + communityAuthority, authority: authority.publicKey, config, systemProgram: anchor.web3.SystemProgram.programId, diff --git a/anchor/tests/utils/create-test-profile.ts b/anchor/tests/utils/create-test-profile.ts index f4c3bfb..44cf99b 100644 --- a/anchor/tests/utils/create-test-profile.ts +++ b/anchor/tests/utils/create-test-profile.ts @@ -3,12 +3,19 @@ import { SystemProgram } from '@solana/web3.js' import { getPubKeyPointerPda, getPubKeyProfilePda, IdentityProvider, PubkeyProtocol } from '../../src' import { getProfileAvatarUrl } from './get-avatar-url' -export async function createTestProfile( - username: string, - program: anchor.Program, - profileOwner: anchor.web3.Keypair, - feePayer: anchor.web3.PublicKey, -) { +export async function createTestProfile({ + community, + communityAuthority, + profileOwner, + program, + username, +}: { + community: anchor.web3.PublicKey + communityAuthority: anchor.web3.Keypair + profileOwner: anchor.web3.Keypair + program: anchor.Program + username: string +}) { try { const [profile] = getPubKeyProfilePda({ username, programId: program.programId }) const [pointer] = getPubKeyPointerPda({ @@ -20,17 +27,18 @@ export async function createTestProfile( await program.methods .profileCreate({ avatarUrl: getProfileAvatarUrl(username), - name: 'Test Verified User', + name: username, username, }) .accountsStrict({ authority: profileOwner.publicKey, - feePayer, + community, + feePayer: communityAuthority.publicKey, profile, pointer, systemProgram: SystemProgram.programId, }) - .signers([profileOwner]) + .signers([communityAuthority, profileOwner]) .rpc() return profile diff --git a/cli/src/commands/get-community-command.ts b/cli/src/commands/get-community-command.ts index 7964034..3bcfd0c 100644 --- a/cli/src/commands/get-community-command.ts +++ b/cli/src/commands/get-community-command.ts @@ -37,7 +37,7 @@ export function getCommunityCommand(): Command { // Prompt the user for the necessary information const name = await ask('Enter the name of the community: ') console.log('Create community functionality goes here', name) - const { authority, cluster, connection, endpoint, feePayer, sdk } = await getConfig() + const { authority, cluster, connection, endpoint, sdk } = await getConfig() const { input, tx: transaction } = await sdk.communityCreate({ authority: authority.publicKey, diff --git a/cli/src/commands/get-provision-command.ts b/cli/src/commands/get-provision-command.ts index 89d4426..90b91e0 100644 --- a/cli/src/commands/get-provision-command.ts +++ b/cli/src/commands/get-provision-command.ts @@ -1,14 +1,9 @@ import { Command } from 'commander' -import { ensureBalance } from '../utils/ensure-balance' -import { getConfig } from '../utils/get-config' import { provisionCommunitiesIfNeeded } from './provision-communities' export function getProvisionCommand(): Command { return new Command('provision').description('Provision communities').action(async () => { try { - const { connection, feePayer } = await getConfig() - - await ensureBalance(connection, feePayer.publicKey) await provisionCommunitiesIfNeeded() } catch (err) { console.error(`An error occurred: ${err}`) diff --git a/cli/src/utils/get-config.ts b/cli/src/utils/get-config.ts index 662ea77..43823e3 100644 --- a/cli/src/utils/get-config.ts +++ b/cli/src/utils/get-config.ts @@ -5,7 +5,6 @@ import { Connection, PublicKey } from '@solana/web3.js' import process from 'node:process' import { getPubkeyProtocolSdk } from '../utils' import { getConfigKeypair } from './get-config-keypair' -import { getFeePayerKeypair } from './get-fee-payer-keypair' export async function getConfig() { const cluster: SolanaCluster = 'local' @@ -13,7 +12,6 @@ export async function getConfig() { const programId = new PublicKey(process.env.PUBKEY_PROTOCOL_PROGRAM_ID || PUBKEY_PROTOCOL_PROGRAM_ID) const connection = new Connection(endpoint, 'confirmed') const sdk = await getPubkeyProtocolSdk({ connection, programId }) - const feePayer = getFeePayerKeypair() const configAuthority = getConfigKeypair() const authority = await getKeypairFromFile(process.env.AUTHORITY_KEYPAIR_PATH) @@ -23,7 +21,6 @@ export async function getConfig() { configAuthority, connection, endpoint, - feePayer, programId, sdk, } diff --git a/cli/src/utils/get-fee-payer-keypair.ts b/cli/src/utils/get-fee-payer-keypair.ts deleted file mode 100644 index 1ba68a1..0000000 --- a/cli/src/utils/get-fee-payer-keypair.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Keypair } from '@solana/web3.js' - -export const FEE_PAYER = [ - 70, 56, 165, 176, 206, 28, 236, 12, 82, 10, 157, 230, 111, 245, 20, 153, 78, 236, 236, 175, 139, 94, 74, 166, 234, - 105, 243, 29, 7, 128, 180, 102, 5, 188, 6, 222, 199, 100, 106, 167, 167, 226, 149, 117, 130, 48, 84, 46, 158, 67, 159, - 201, 180, 76, 27, 163, 29, 203, 181, 210, 8, 28, 97, 48, -] - -export function getFeePayerKeypair() { - return Keypair.fromSecretKey(Uint8Array.from(FEE_PAYER)) -} diff --git a/sdk/src/lib/pubkey-protocol-sdk.ts b/sdk/src/lib/pubkey-protocol-sdk.ts index 1418ac8..c0c87a8 100644 --- a/sdk/src/lib/pubkey-protocol-sdk.ts +++ b/sdk/src/lib/pubkey-protocol-sdk.ts @@ -39,85 +39,91 @@ export interface PubKeyProfileSdkOptions { readonly provider: AnchorProvider } -export interface GetProfileByProvider { - provider: IdentityProvider - providerId: string -} - -export interface GetProfileByUsername { - username: string +export interface CommunityCreateInput { + avatarUrl: string + name: string + slug: string } -export interface AddIdentityOptions { +export interface CommunityCreateOptions { + communityAuthority: PublicKey authority: PublicKey - feePayer: PublicKey - username: string - providerId: string - provider: IdentityProvider + avatarUrl?: string name: string + slug?: string } -export interface RemoveIdentityOptions { +export interface CommunityUpdateOptions { authority: PublicKey + avatarUrl?: string + discord?: string + farcaster?: string feePayer: PublicKey - username: string - providerId: string - provider: IdentityProvider + github?: string + name?: string + slug: string + telegram?: string + website?: string + x?: string } -export interface RemoveAuthorityOptions { - authorityToRemove: PublicKeyString +export interface ProfileAuthorityRemoveOptions { authority: PublicKeyString + authorityToRemove: PublicKeyString + community: PublicKeyString feePayer: PublicKeyString username: string } -export interface AddAuthorityOptions { - newAuthority: PublicKeyString +export interface ProfileAuthorityAddOptions { authority: PublicKeyString + community: PublicKeyString feePayer: PublicKeyString + newAuthority: PublicKeyString username: string } -export interface CommunityCreateInput { - avatarUrl: string +export interface ProfileCreateOptions { + avatarUrl?: string + authority: PublicKey + community: PublicKey + feePayer: PublicKey name: string - slug: string + username?: string } -export interface CommunityCreateOptions { - communityAuthority: PublicKey - authority: PublicKey - avatarUrl?: string - name: string - slug?: string +export interface ProfileGetByProvider { + provider: IdentityProvider + providerId: string } -export interface ProfileCreateOptions { - avatarUrl?: string +export interface ProfileGetByUsername { + username: string +} + +export interface ProfileIdentityAddOptions { authority: PublicKey + community: PublicKey feePayer: PublicKey name: string - username?: string + provider: IdentityProvider + providerId: string + username: string } -export interface CommunityUpdateOptions { +export interface ProfileIdentityRemoveOptions { authority: PublicKey - avatarUrl?: string - discord?: string - farcaster?: string + community: PublicKey feePayer: PublicKey - github?: string - name?: string - slug: string - telegram?: string - website?: string - x?: string + username: string + providerId: string + provider: IdentityProvider } -export interface ProfileUpdateOptions { +export interface ProfileUpdateDetailsOptions { avatarUrl: string authority: PublicKeyString + community: PublicKeyString feePayer: PublicKeyString name: string username: string @@ -422,7 +428,6 @@ export class PubkeyProtocolSdk { ...identity, provider: convertAnchorIdentityProvider(identity.provider), })), - feePayer: account.feePayer, name: account.name, username: account.username, })), @@ -462,7 +467,7 @@ export class PubkeyProtocolSdk { }) } - async profileGetByProvider(options: GetProfileByProvider): Promise { + async profileGetByProvider(options: ProfileGetByProvider): Promise { const [pointer] = this.pdaPointer(options) const { profile } = await this.pointerGet({ pointer }) @@ -470,7 +475,7 @@ export class PubkeyProtocolSdk { return this.profileGet({ profile }) } - async profileGetByProviderNullable(options: GetProfileByProvider): Promise { + async profileGetByProviderNullable(options: ProfileGetByProvider): Promise { const [pointer] = this.pdaPointer(options) const { profile } = await this.pointerGet({ pointer }) @@ -478,21 +483,22 @@ export class PubkeyProtocolSdk { return this.profileGetNullable({ profile }) } - async profileGetByUsername(options: GetProfileByUsername): Promise { + async profileGetByUsername(options: ProfileGetByUsername): Promise { const [profile] = this.pdaProfile({ username: options.username }) return this.profileGet({ profile }) } - async profileGetByUsernameNullable(options: GetProfileByUsername): Promise { + async profileGetByUsernameNullable(options: ProfileGetByUsername): Promise { const [profile] = this.pdaProfile({ username: options.username }) return this.profileGetNullable({ profile }) } - async profileAuthorityAdd(options: AddAuthorityOptions) { + async profileAuthorityAdd(options: ProfileAuthorityAddOptions) { const [profile] = this.pdaProfile({ username: options.username }) const authority = new PublicKey(options.authority) + const community = new PublicKey(options.community) const feePayer = new PublicKey(options.feePayer) const newAuthority = new PublicKey(options.newAuthority) @@ -501,6 +507,7 @@ export class PubkeyProtocolSdk { .accountsStrict({ authority, feePayer, + community, profile, systemProgram: SystemProgram.programId, }) @@ -509,15 +516,21 @@ export class PubkeyProtocolSdk { return this.createTransaction({ ix, feePayer }) } - async profileAuthorityRemove(options: RemoveAuthorityOptions) { + async profileAuthorityRemove(options: ProfileAuthorityRemoveOptions) { const [profile] = this.pdaProfile({ username: options.username }) const authorityToRemove = new PublicKey(options.authorityToRemove) const authority = new PublicKey(options.authority) + const community = new PublicKey(options.community) const feePayer = new PublicKey(options.feePayer) const ix = await this.program.methods .profileAuthorityRemove({ authorityToRemove }) - .accountsStrict({ authority, feePayer, profile }) + .accountsStrict({ + authority, + community, + feePayer, + profile, + }) .instruction() return this.createTransaction({ ix, feePayer }) @@ -525,6 +538,7 @@ export class PubkeyProtocolSdk { async profileCreate(options: ProfileCreateOptions) { const username = options.username?.length ? options.username : slugify(options.name) + const community = new PublicKey(options.community) const [profile] = this.pdaProfile({ username }) const [pointer] = this.pdaPointer({ provider: IdentityProvider.Solana, @@ -538,6 +552,7 @@ export class PubkeyProtocolSdk { }) .accountsStrict({ authority: options.authority, + community, feePayer: options.feePayer, pointer, profile, @@ -548,23 +563,33 @@ export class PubkeyProtocolSdk { return this.createTransaction({ ix, feePayer: options.feePayer }) } - async profileUpdate(options: ProfileUpdateOptions) { + async profileUpdateDetails(options: ProfileUpdateDetailsOptions) { const [profile] = this.pdaProfile({ username: options.username }) const authority = new PublicKey(options.authority) + const community = new PublicKey(options.community) const feePayer = new PublicKey(options.feePayer) const ix = await this.program.methods - .profileUpdateDetails({ newAvatarUrl: options.avatarUrl, newName: options.name, authority }) - .accounts({ feePayer, profile }) + .profileUpdateDetails({ + newAvatarUrl: options.avatarUrl, + newName: options.name, + }) + .accountsStrict({ + authority, + community, + feePayer, + profile, + }) .instruction() return this.createTransaction({ ix, feePayer }) } - async profileIdentityAdd(options: AddIdentityOptions) { + async profileIdentityAdd(options: ProfileIdentityAddOptions) { const [profile] = this.pdaProfile({ username: options.username }) const [pointer] = this.pdaPointer({ providerId: options.providerId, provider: options.provider }) const authority = new PublicKey(options.authority) + const community = new PublicKey(options.community) const feePayer = new PublicKey(options.feePayer) const ix = await this.program.methods @@ -575,6 +600,7 @@ export class PubkeyProtocolSdk { }) .accountsStrict({ authority, + community, feePayer, profile, pointer, @@ -585,16 +611,18 @@ export class PubkeyProtocolSdk { return this.createTransaction({ ix, feePayer }) } - async profileIdentityRemove(options: RemoveIdentityOptions) { + async profileIdentityRemove(options: ProfileIdentityRemoveOptions) { const [profile] = this.pdaProfile({ username: options.username }) const [pointer] = this.pdaPointer({ providerId: options.providerId, provider: options.provider }) const authority = new PublicKey(options.authority) + const community = new PublicKey(options.community) const feePayer = new PublicKey(options.feePayer) const ix = await this.program.methods .removeIdentity({ providerId: options.providerId }) .accountsStrict({ authority, + community, feePayer, pointer, profile, diff --git a/web/src/app/features/dev/data-access/convert-to-map.ts b/web/src/app/features/dev/data-access/convert-to-map.ts index 28e330a..951fc38 100644 --- a/web/src/app/features/dev/data-access/convert-to-map.ts +++ b/web/src/app/features/dev/data-access/convert-to-map.ts @@ -1,13 +1,4 @@ -import { PubKeyCommunity, PubKeyProfile } from '@pubkey-protocol/anchor' - -export type PubKeyCommunityMap = Record - -export function convertPubKeyCommunitiesToMap(communities: PubKeyCommunity[]): PubKeyCommunityMap { - return communities.reduce( - (map, community) => ({ ...map, [community.publicKey.toString()]: community }), - {} as PubKeyCommunityMap, - ) -} +import { PubKeyProfile } from '@pubkey-protocol/anchor' export type PubKeyProfileMap = Record diff --git a/web/src/app/features/dev/data-access/dev-data-communities.ts b/web/src/app/features/dev/data-access/dev-data-communities.ts index 2f63142..a287ab2 100644 --- a/web/src/app/features/dev/data-access/dev-data-communities.ts +++ b/web/src/app/features/dev/data-access/dev-data-communities.ts @@ -1,6 +1,7 @@ import { PubKeyCommunity } from '@pubkey-protocol/anchor' import { PublicKey } from '@solana/web3.js' -import { convertPubKeyCommunitiesToMap, PubKeyCommunityMap } from './convert-to-map' +import { convertPubKeyCommunitiesToMap, PubKeyCommunityMap } from '../../pubkey-community/data-access' + import { createDevCommunity } from './index' export const pubkeyPda = new PublicKey('PubkntyGPcm1D7WHjzwiFUpfiRvBKd4YLRYuWwrWgC2') diff --git a/web/src/app/features/dev/data-access/index.ts b/web/src/app/features/dev/data-access/index.ts index edf29ab..4059f7c 100644 --- a/web/src/app/features/dev/data-access/index.ts +++ b/web/src/app/features/dev/data-access/index.ts @@ -53,7 +53,6 @@ export function createDevProfile({ return { bump: 0, - feePayer: PublicKey.default, identities, publicKey, name, diff --git a/web/src/app/features/pubkey-community/data-access/index.ts b/web/src/app/features/pubkey-community/data-access/index.ts index 0ddc35d..0dce00d 100644 --- a/web/src/app/features/pubkey-community/data-access/index.ts +++ b/web/src/app/features/pubkey-community/data-access/index.ts @@ -1,3 +1,4 @@ +export * from './pubkey-community-provider' export * from './use-community-is-authority-connected' export * from './use-mutation-community-create' export * from './use-mutation-community-provider-disable' diff --git a/web/src/app/features/pubkey-community/data-access/pubkey-community-provider.tsx b/web/src/app/features/pubkey-community/data-access/pubkey-community-provider.tsx new file mode 100644 index 0000000..5cb394f --- /dev/null +++ b/web/src/app/features/pubkey-community/data-access/pubkey-community-provider.tsx @@ -0,0 +1,66 @@ +import { PubKeyCommunity } from '@pubkey-protocol/anchor' +import { UiError, UiLoader } from '@pubkey-ui/core' +import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react' +import { useQueryCommunityGetAll } from './use-query-community-get-all' + +export type PubKeyCommunityMap = Record + +export function convertPubKeyCommunitiesToMap(communities: PubKeyCommunity[]): PubKeyCommunityMap { + return communities.reduce( + (map, community) => ({ ...map, [community.publicKey.toString()]: community }), + {} as PubKeyCommunityMap, + ) +} + +export interface PubKeyCommunityProviderContext { + communities: PubKeyCommunity[] + community: PubKeyCommunity + communityMap: PubKeyCommunityMap + setCommunity: (community: PubKeyCommunity) => void +} + +const Context = createContext({} as PubKeyCommunityProviderContext) + +export function PubKeyCommunityProvider({ children }: { children: ReactNode }) { + const query = useQueryCommunityGetAll() + + const [community, setCommunity] = useState(undefined) + + useEffect(() => { + if (community || !query.data?.length) { + return + } + if (query.data.length) { + setCommunity(query.data[0]) + } + }, [query.data, community]) + + const communities = useMemo(() => query.data ?? [], [query.data]) + const communityMap = useMemo(() => convertPubKeyCommunitiesToMap(query.data ?? []), [query.data]) + + if (query.isLoading) { + return + } + + if (!query.data) { + return + } + if (!community) { + return + } + + const value: PubKeyCommunityProviderContext = { + communities, + communityMap, + community, + setCommunity: (community: PubKeyCommunity) => { + setCommunity(community) + }, + } + + return {children} +} + +export function usePubKeyCommunity() { + return useContext(Context) +} diff --git a/web/src/app/features/pubkey-community/ui/index.ts b/web/src/app/features/pubkey-community/ui/index.ts index c32fe4c..166be75 100644 --- a/web/src/app/features/pubkey-community/ui/index.ts +++ b/web/src/app/features/pubkey-community/ui/index.ts @@ -6,6 +6,7 @@ export * from './pubkey-protocol-ui-community-grid' export * from './pubkey-protocol-ui-community-header' export * from './pubkey-protocol-ui-community-item' export * from './pubkey-protocol-ui-community-link-item' +export * from './pubkey-protocol-ui-community-select' export * from './pubkey-protocol-ui-community-signer-form' export * from './pubkey-protocol-ui-community-socials' export * from './pubkey-protocol-ui-community-update-form' diff --git a/web/src/app/features/pubkey-community/ui/pubkey-protocol-ui-community-select.tsx b/web/src/app/features/pubkey-community/ui/pubkey-protocol-ui-community-select.tsx new file mode 100644 index 0000000..8b977ba --- /dev/null +++ b/web/src/app/features/pubkey-community/ui/pubkey-protocol-ui-community-select.tsx @@ -0,0 +1,40 @@ +import { Button, Menu } from '@mantine/core' +import { Link } from 'react-router-dom' +import { usePubKeyCommunity } from '../data-access' +import { PubkeyProtocolUiCommunityAvatar } from './pubkey-protocol-ui-community-avatar' + +export function PubkeyProtocolUiCommunitySelect() { + const { communities, community, setCommunity } = usePubKeyCommunity() + return ( + + + + + + + {communities.length ? ( + communities.map((item) => ( + setCommunity(item)} + leftSection={} + > + {item.name} + + )) + ) : ( + + Manage Communities + + )} + + + ) +} diff --git a/web/src/app/features/pubkey-profile/data-access/use-mutation-add-identity.tsx b/web/src/app/features/pubkey-profile/data-access/use-mutation-add-identity.tsx index f036cb5..4fb0d8d 100644 --- a/web/src/app/features/pubkey-profile/data-access/use-mutation-add-identity.tsx +++ b/web/src/app/features/pubkey-profile/data-access/use-mutation-add-identity.tsx @@ -1,15 +1,16 @@ -import { AddIdentityOptions } from '@pubkey-protocol/sdk' +import { ProfileIdentityAddOptions } from '@pubkey-protocol/sdk' +import { PublicKey } from '@solana/web3.js' import { useMutation } from '@tanstack/react-query' import { usePubKeyProtocol } from '../../pubkey-protocol' -export type PubKeyAddIdentity = Omit +export type PubKeyAddIdentity = Omit -export function useMutationAddIdentity() { +export function useMutationAddIdentity({ community }: { community: PublicKey }) { const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() return useMutation({ mutationFn: (options: PubKeyAddIdentity) => - sdk.profileIdentityAdd({ ...options, authority, feePayer }).then(signAndConfirmTransaction), + sdk.profileIdentityAdd({ ...options, authority, community, feePayer }).then(signAndConfirmTransaction), onError, onSuccess, }) diff --git a/web/src/app/features/pubkey-profile/data-access/use-mutation-create-profile.tsx b/web/src/app/features/pubkey-profile/data-access/use-mutation-create-profile.tsx index 4d78292..8600da2 100644 --- a/web/src/app/features/pubkey-profile/data-access/use-mutation-create-profile.tsx +++ b/web/src/app/features/pubkey-profile/data-access/use-mutation-create-profile.tsx @@ -1,10 +1,11 @@ import { ProfileCreateOptions } from '@pubkey-protocol/sdk' +import { PublicKey } from '@solana/web3.js' import { useMutation } from '@tanstack/react-query' import { usePubKeyProtocol } from '../../pubkey-protocol' -export type PubKeyProfileCreateInput = Omit +export type PubKeyProfileCreateInput = Omit -export function useMutationProfileCreate() { +export function useMutationProfileCreate({ community }: { community: PublicKey }) { const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() return useMutation({ @@ -12,6 +13,7 @@ export function useMutationProfileCreate() { sdk .profileCreate({ ...options, + community, authority, feePayer, }) diff --git a/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-add.tsx b/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-add.tsx index c13128b..81040ed 100644 --- a/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-add.tsx +++ b/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-add.tsx @@ -1,12 +1,14 @@ -import { AddAuthorityOptions } from '@pubkey-protocol/sdk' +import { ProfileAuthorityAddOptions } from '@pubkey-protocol/sdk' +import { PublicKey } from '@solana/web3.js' import { useMutation } from '@tanstack/react-query' import { usePubKeyProtocol } from '../../pubkey-protocol' -export function useMutationProfileAuthorityAdd() { +export function useMutationProfileAuthorityAdd({ community }: { community: PublicKey }) { const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() return useMutation({ - mutationFn: (options: AddAuthorityOptions) => sdk.profileAuthorityAdd(options).then(signAndConfirmTransaction), + mutationFn: (options: Omit) => + sdk.profileAuthorityAdd({ ...options, community }).then(signAndConfirmTransaction), onError, onSuccess, }) diff --git a/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-remove.tsx b/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-remove.tsx index 3d3fb1d..d22744e 100644 --- a/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-remove.tsx +++ b/web/src/app/features/pubkey-profile/data-access/use-mutation-profile-authority-remove.tsx @@ -1,13 +1,14 @@ -import { RemoveAuthorityOptions } from '@pubkey-protocol/sdk' +import { ProfileAuthorityRemoveOptions } from '@pubkey-protocol/sdk' +import { PublicKey } from '@solana/web3.js' import { useMutation } from '@tanstack/react-query' import { usePubKeyProtocol } from '../../pubkey-protocol' -export function useMutationProfileAuthorityRemove() { +export function useMutationProfileAuthorityRemove({ community }: { community: PublicKey }) { const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() return useMutation({ - mutationFn: (options: RemoveAuthorityOptions) => - sdk.profileAuthorityRemove(options).then(signAndConfirmTransaction), + mutationFn: (options: Omit) => + sdk.profileAuthorityRemove({ ...options, community }).then(signAndConfirmTransaction), onError, onSuccess, }) diff --git a/web/src/app/features/pubkey-profile/data-access/use-mutation-remove-identity.tsx b/web/src/app/features/pubkey-profile/data-access/use-mutation-remove-identity.tsx index a91cade..bedeac1 100644 --- a/web/src/app/features/pubkey-profile/data-access/use-mutation-remove-identity.tsx +++ b/web/src/app/features/pubkey-profile/data-access/use-mutation-remove-identity.tsx @@ -1,12 +1,14 @@ -import { RemoveIdentityOptions } from '@pubkey-protocol/sdk' +import { ProfileIdentityRemoveOptions } from '@pubkey-protocol/sdk' +import { PublicKey } from '@solana/web3.js' import { useMutation } from '@tanstack/react-query' import { usePubKeyProtocol } from '../../pubkey-protocol' -export function useMutationRemoveIdentity() { +export function useMutationRemoveIdentity({ community }: { community: PublicKey }) { const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() return useMutation({ - mutationFn: (options: RemoveIdentityOptions) => sdk.profileIdentityRemove(options).then(signAndConfirmTransaction), + mutationFn: (options: Omit) => + sdk.profileIdentityRemove({ ...options, community }).then(signAndConfirmTransaction), onError, onSuccess, }) diff --git a/web/src/app/features/pubkey-profile/data-access/use-mutation-update-avatar-url.tsx b/web/src/app/features/pubkey-profile/data-access/use-mutation-update-avatar-url.tsx index 22b64b4..cb1ab25 100644 --- a/web/src/app/features/pubkey-profile/data-access/use-mutation-update-avatar-url.tsx +++ b/web/src/app/features/pubkey-profile/data-access/use-mutation-update-avatar-url.tsx @@ -1,12 +1,16 @@ -import { ProfileUpdateOptions } from '@pubkey-protocol/sdk' +import { ProfileUpdateDetailsOptions } from '@pubkey-protocol/sdk' +import { PublicKey } from '@solana/web3.js' import { useMutation } from '@tanstack/react-query' import { usePubKeyProtocol } from '../../pubkey-protocol' -export function useMutationUpdateAvatarUrl() { - const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() +export function useMutationUpdateAvatarUrl({ community }: { community: PublicKey }) { + const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol() return useMutation({ - mutationFn: (options: ProfileUpdateOptions) => sdk.profileUpdate(options).then(signAndConfirmTransaction), + mutationFn: (options: Omit) => + sdk + .profileUpdateDetails({ ...options, authority, feePayer, community }) + .then((x) => signAndConfirmTransaction(x, { withFeePayer: false })), onError, onSuccess, }) diff --git a/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-create.tsx b/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-create.tsx index c8a5089..76a8f97 100644 --- a/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-create.tsx +++ b/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-create.tsx @@ -1,12 +1,14 @@ -import { IdentityProvider } from '@pubkey-protocol/anchor' +import { IdentityProvider, PubKeyCommunity } from '@pubkey-protocol/anchor' import { ellipsify } from '@pubkey-protocol/sdk' import { toastError, toastSuccess, UiCard, UiInfo, UiLoader } from '@pubkey-ui/core' import { usePubKeyProtocol } from '../../pubkey-protocol' import { useMutationProfileCreate, useQueryGetProfileByProviderNullable } from '../data-access' import { PubkeyProtocolUiProfileCreateForm } from '../ui' -export function PubkeyProfileFeatureCreate() { - const mutation = useMutationProfileCreate() +export function PubkeyProfileFeatureCreate({ community }: { community: PubKeyCommunity }) { + const mutation = useMutationProfileCreate({ + community: community.publicKey, + }) const { authority } = usePubKeyProtocol() const pointerQuery = useQueryGetProfileByProviderNullable({ provider: IdentityProvider.Solana, diff --git a/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-detail.tsx b/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-detail.tsx index e33eeb2..48884bd 100644 --- a/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-detail.tsx +++ b/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-detail.tsx @@ -1,9 +1,10 @@ +import { PubKeyCommunity } from '@pubkey-protocol/anchor' import { UiLoader, UiStack, UiWarning } from '@pubkey-ui/core' import { useParams } from 'react-router-dom' import { useQueryGetProfileByUsername } from '../data-access' import { PubkeyProtocolUiProfileCard } from '../ui' -export function PubkeyProfileFeatureDetail() { +export function PubkeyProfileFeatureDetail({ community }: { community: PubKeyCommunity }) { const { username } = useParams() as { username: string } const query = useQueryGetProfileByUsername({ username }) @@ -13,7 +14,7 @@ export function PubkeyProfileFeatureDetail() { {query.isLoading ? ( ) : query.data ? ( - + ) : ( )} diff --git a/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-search.tsx b/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-search.tsx index fcca7c2..d1a69af 100644 --- a/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-search.tsx +++ b/web/src/app/features/pubkey-profile/feature/pubkey-profile-feature-search.tsx @@ -1,7 +1,7 @@ import { ActionIcon, Select, TextInput } from '@mantine/core' import { useForm } from '@mantine/form' import { IdentityProvider, PubKeyProfile } from '@pubkey-protocol/anchor' -import { GetProfileByProvider, GetProfileByUsername } from '@pubkey-protocol/sdk' +import { ProfileGetByProvider, ProfileGetByUsername } from '@pubkey-protocol/sdk' import { toastError, toastInfo, toastSuccess, UiCard, UiStack } from '@pubkey-ui/core' import { IconSearch } from '@tabler/icons-react' import { useState } from 'react' @@ -25,14 +25,14 @@ export function PubkeyProfileFeatureSearch() { function SearchByProvider() { const [result, setResult] = useState(null) const { sdk } = usePubKeyProtocol() - const form = useForm({ + const form = useForm({ initialValues: { provider: IdentityProvider.Solana, providerId: '', }, }) - async function submit({ provider, providerId }: GetProfileByProvider) { + async function submit({ provider, providerId }: ProfileGetByProvider) { setResult(null) sdk .profileGetByProvider({ provider, providerId }) @@ -80,9 +80,9 @@ function SearchByProvider() { function SearchByUsername() { const [result, setResult] = useState(null) const { sdk } = usePubKeyProtocol() - const form = useForm({ initialValues: { username: '' } }) + const form = useForm({ initialValues: { username: '' } }) - async function submit({ username }: GetProfileByUsername) { + async function submit({ username }: ProfileGetByUsername) { setResult(null) sdk .profileGetByUsernameNullable({ username }) diff --git a/web/src/app/features/pubkey-profile/feature/pubkey-profile.routes.tsx b/web/src/app/features/pubkey-profile/feature/pubkey-profile.routes.tsx index a5bfad4..5013ab0 100644 --- a/web/src/app/features/pubkey-profile/feature/pubkey-profile.routes.tsx +++ b/web/src/app/features/pubkey-profile/feature/pubkey-profile.routes.tsx @@ -2,6 +2,11 @@ import { Button, Group } from '@mantine/core' import { UiPage } from '@pubkey-ui/core' import { IconUser } from '@tabler/icons-react' import { Link, useRoutes } from 'react-router-dom' +import { + PubKeyCommunityProvider, + usePubKeyCommunity, +} from '../../pubkey-community/data-access/pubkey-community-provider' +import { PubkeyProtocolUiCommunitySelect } from '../../pubkey-community/ui' import { PubKeyProtocolLoader } from '../../pubkey-protocol' import { PubkeyProfileFeatureCreate } from './pubkey-profile-feature-create' @@ -10,31 +15,41 @@ import { PubkeyProfileFeatureList } from './pubkey-profile-feature-list' import { PubkeyProfileFeatureSearch } from './pubkey-profile-feature-search' export default function PubkeyProfileRoutes({ basePath }: { basePath: string }) { + return ( + + + + + + ) +} + +export function Router({ basePath }: { basePath: string }) { + const { community } = usePubKeyCommunity() const routes = useRoutes([ { index: true, element: }, - { path: 'create', element: }, + { path: 'create', element: }, { path: 'search', element: }, - { path: ':username', element: }, + { path: ':username', element: }, ]) return ( - - } - title="Profiles" - rightAction={ - - - - - } - > - {routes} - - + } + title="Profiles" + rightAction={ + + + + + + } + > + {routes} + ) } diff --git a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-identity.tsx b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-identity.tsx index 16da6b1..af5e58f 100644 --- a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-identity.tsx +++ b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-identity.tsx @@ -1,26 +1,45 @@ -import { Group, Stack, Text } from '@mantine/core' +import { Divider, Group, Text } from '@mantine/core' import { PubKeyIdentity } from '@pubkey-protocol/anchor' import { ellipsify } from '@pubkey-protocol/sdk' -import { UiAnchor } from '@pubkey-ui/core' +import { UiAnchor, UiGroup, UiStack } from '@pubkey-ui/core' +import { ReactNode } from 'react' +import { usePubKeyCommunity } from '../../pubkey-community/data-access' +import { PubkeyProtocolUiCommunityLinkItem } from '../../pubkey-community/ui' import { usePubKeyProtocol } from '../../pubkey-protocol' import { PubkeyProtocolUiIdentityProviderIcon } from './pubkey-protocol-ui-identity-provider-icon' -export function PubkeyProtocolUiIdentity({ identity }: { identity: PubKeyIdentity }) { +export function PubkeyProtocolUiIdentity({ action, identity }: { action?: ReactNode; identity: PubKeyIdentity }) { const { getIdentityUrl } = usePubKeyProtocol() + const { communityMap } = usePubKeyCommunity() + const url = getIdentityUrl(identity) return ( - - - - - - {identity.name} + + + + + + {identity.provider.toString()} - - - {ellipsify(identity.providerId, 10)} - - + + + + + {identity.name} + + {action} + + + {ellipsify(identity.providerId.toString(), 8)} + + + + {identity.communities?.map((x) => ( + + ))} + + + ) } diff --git a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-authority-remove-button.tsx b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-authority-remove-button.tsx index d3409f7..c2905ec 100644 --- a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-authority-remove-button.tsx +++ b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-authority-remove-button.tsx @@ -1,5 +1,5 @@ import { ActionIcon } from '@mantine/core' -import { PubKeyProfile } from '@pubkey-protocol/anchor' +import { PubKeyCommunity, PubKeyProfile } from '@pubkey-protocol/anchor' import { PublicKey } from '@solana/web3.js' import { IconTrash } from '@tabler/icons-react' import { useMutationProfileAuthorityRemove } from '../data-access' @@ -7,15 +7,19 @@ import { useMutationProfileAuthorityRemove } from '../data-access' export function PubkeyProtocolUiProfileAuthorityRemoveButton({ authorityToRemove, authority, + community, feePayer, profile, }: { authorityToRemove: PublicKey authority: PublicKey + community: PubKeyCommunity feePayer: PublicKey profile: PubKeyProfile }) { - const mutation = useMutationProfileAuthorityRemove() + const mutation = useMutationProfileAuthorityRemove({ + community: community.publicKey, + }) return ( @@ -38,6 +41,7 @@ export function PubkeyProtocolUiProfileCardAuthorities({ diff --git a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card-identities.tsx b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card-identities.tsx index 5b43781..af023d9 100644 --- a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card-identities.tsx +++ b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card-identities.tsx @@ -1,5 +1,5 @@ -import { PubKeyProfile } from '@pubkey-protocol/anchor' -import { UiGroup } from '@pubkey-ui/core' +import { PubKeyCommunity, PubKeyProfile } from '@pubkey-protocol/anchor' +import { UiStack } from '@pubkey-ui/core' import { PublicKey } from '@solana/web3.js' import { UiAppCard } from '../../../ui' import { useKeypair } from '../../keypair/data-access' @@ -8,9 +8,11 @@ import { PubkeyProtocolUiProfileButtonAddIdentity } from './pubkey-protocol-ui-p import { PubkeyProtocolUiProfileButtonRemoveIdentity } from './pubkey-protocol-ui-profile-button-remove-identity' export function PubkeyProtocolUiProfileCardIdentities({ + community, profile, signAuthority, }: { + community: PubKeyCommunity profile: PubKeyProfile signAuthority: PublicKey }) { @@ -20,22 +22,28 @@ export function PubkeyProtocolUiProfileCardIdentities({ return ( : null} + action={canSign ? : null} > - {profile.identities.map((item) => ( - - - {canSign ? ( - - ) : null} - - ))} + + {profile.identities.map((item) => ( + + ) : null + } + > + ))} + ) } diff --git a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card.tsx b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card.tsx index bb8eb89..dc8169a 100644 --- a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card.tsx +++ b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-card.tsx @@ -1,5 +1,5 @@ import { Code, Group, Stack } from '@mantine/core' -import { PubKeyProfile } from '@pubkey-protocol/anchor' +import { PubKeyCommunity, PubKeyProfile } from '@pubkey-protocol/anchor' import { ellipsify } from '@pubkey-protocol/sdk' import { UiCard, UiDebugModal, UiGroup, UiStack } from '@pubkey-ui/core' import { PublicKey } from '@solana/web3.js' @@ -12,7 +12,13 @@ import { PubkeyProtocolUiProfileAvatarUpdateButton } from './pubkey-protocol-ui- import { PubkeyProtocolUiProfileCardAuthorities } from './pubkey-protocol-ui-profile-card-authorities' import { PubkeyProtocolUiProfileCardIdentities } from './pubkey-protocol-ui-profile-card-identities' -export function PubkeyProtocolUiProfileCard({ profile }: { profile: PubKeyProfile }) { +export function PubkeyProtocolUiProfileCard({ + community, + profile, +}: { + community: PubKeyCommunity + profile: PubKeyProfile +}) { const { authority } = usePubKeyProtocol() const signAuthority = useMemo( @@ -45,9 +51,21 @@ export function PubkeyProtocolUiProfileCard({ profile }: { profile: PubKeyProfil {signAuthority ? ( - - - + + + ) : null} diff --git a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-profile-authority-add-button.tsx b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-profile-authority-add-button.tsx index 977c335..add435a 100644 --- a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-profile-authority-add-button.tsx +++ b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile-profile-authority-add-button.tsx @@ -1,18 +1,20 @@ import { Button } from '@mantine/core' -import { PubKeyProfile } from '@pubkey-protocol/anchor' +import { PubKeyCommunity, PubKeyProfile } from '@pubkey-protocol/anchor' import { PublicKey } from '@solana/web3.js' import { useMutationProfileAuthorityAdd } from '../data-access' export function PubkeyProtocolUiProfileProfileAuthorityAddButton({ authority, + community, feePayer, profile: { username }, }: { authority: PublicKey + community: PubKeyCommunity feePayer: PublicKey profile: PubKeyProfile }) { - const mutation = useMutationProfileAuthorityAdd() + const mutation = useMutationProfileAuthorityAdd({ community: community.publicKey }) function submit() { const newAuthority = window.prompt('Enter the new authority', authority.toString()) diff --git a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile.tsx b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile.tsx index ef6b42f..d439264 100644 --- a/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile.tsx +++ b/web/src/app/features/pubkey-profile/ui/pubkey-protocol-ui-profile.tsx @@ -1,4 +1,4 @@ -import { Group, Stack } from '@mantine/core' +import { Code, Group, Stack } from '@mantine/core' import { PubKeyProfile } from '@pubkey-protocol/anchor' import { UiDebugModal } from '@pubkey-ui/core' import { ReactNode } from 'react' @@ -21,7 +21,10 @@ export function PubkeyProtocolUiProfile({ - + + {profile.username} + + {profile.identities?.map((identity) => ( ))} diff --git a/web/src/app/features/pubkey-protocol/data-access/pubkey-protocol-provider.tsx b/web/src/app/features/pubkey-protocol/data-access/pubkey-protocol-provider.tsx index aa4d2dc..4fcb08e 100644 --- a/web/src/app/features/pubkey-protocol/data-access/pubkey-protocol-provider.tsx +++ b/web/src/app/features/pubkey-protocol/data-access/pubkey-protocol-provider.tsx @@ -96,8 +96,6 @@ export function PubKeyProtocolProvider({ return `https://warpcast.com/${identity.name}` case IdentityProvider.Github: return `https://github.com/${identity.name}` - case IdentityProvider.Google: - return `https://google.com/${identity.name}` case IdentityProvider.Solana: return getExplorerUrl(`address/${identity.providerId}`) case IdentityProvider.Telegram: