Player Profile

@staratlas/player-profile

Introduction

Welcome to the Player Profile, brought to you by Star Atlas! This open protocol empowers developers to seamlessly create and manage decentralized profiles on the Solana blockchain. Dive into a world where game and application profiles are not bound by central control, ensuring greater trust, transparency, and user autonomy.

The Player Profile Program allows users to manage their identity and access keys. Users can create a list of public keys associated with their profile and set permissions for each key. Identity is no longer tied to a key and is instead tied to a profile, allowing for key rotation, complex ownership structures, and more. No CPI is required to check a key that signs on behalf of a profile.

Package
Description
Version
Docs

@staratlas/player-profile

TypeScript bindings for the player-profile program

npm

Docs

Installation

npm i @staratlas/player-profile

Examples

This guide provides a detailed overview and step‑by‑step instructions on using the player_profile program. It covers program details, environment setup, sample scripts, instruction usage, account structures, error definitions, key constants, and PDA derivations.

──────────────────────────────

1. Program Details

Program Name:

player_profile

Version:

0.7.3

Program Address: • pprofELXjL5Kck7Jn5hCpwAL82DpTkSYBENzahVtbc9

Description:

The player_profile program manages a complex on‑chain identity system. It decouples identity from signing keys by using a profile that stores multiple keys with associated permissions. Typical use cases include key rotation, assigning different roles to players, and enforcing multi‑signature authority over profile changes. The program further supports role creation and membership management, making it ideal for decentralized applications (DApps) where flexible identity management is required.

IDL Reference:

For a complete specification, please see the attached IDL file:

player_profile.json

──────────────────────────────

2. Environment Setup & Loading the Program

Required Dependencies

  • Anchor CLI: Version ≥ 0.24 (or as specified in your workspace)

  • Solana CLI: Latest stable version recommended

Sample TypeScript Code for Setup

Below is a sample TypeScript snippet that imports the required dependencies, sets up the Anchor provider, and loads the program:

import * as anchor from '@project-serum/anchor';
import { Program, Provider, web3 } from '@project-serum/anchor';
import { player_profile, IDL } from './player_profile'; // Adjust the path to your generated IDL

// Set the provider. Ensure that your environment's Solana config is properly configured.
const provider = Provider.env();
anchor.setProvider(provider);

// Load the program from the workspace using the IDL and program address.
const programID = new web3.PublicKey("pprofELXjL5Kck7Jn5hCpwAL82DpTkSYBENzahVtbc9");
const program = new Program<IDL>(IDL, programID, provider);

// Now you can use `program` to call instructions and fetch on‑chain data.

──────────────────────────────

3. Sample Scripts/Quick Start

Below are examples showing how to fetch on‑chain data using TypeScript.

Fetch a Profile Account

async function fetchProfile(profilePubkey: web3.PublicKey) {
  // Fetch the on‑chain account data for a profile
  try {
    const profileAccount = await program.account.profile.fetch(profilePubkey);
    console.log("Profile:", profileAccount);
  } catch (e) {
    console.error("Error fetching profile account:", e);
  }
}

Set a Profile Name

async function setProfileName(profilePubkey: web3.PublicKey, keyIndex: number, newName: string) {
  // Derive PlayerName PDA using the profile pubkey (see PDA section below)
  const [playerNamePda, nameBump] = await web3.PublicKey.findProgramAddress(
    [Buffer.from("player_name"), profilePubkey.toBuffer()],
    program.programId
  );

  // Call setName instruction with required accounts and parameter
  await program.rpc.setName(new anchor.BN(keyIndex), Buffer.from(newName), {
    accounts: {
      key: /* the signer authorized with CHANGE_NAME permission */,
      funder: provider.wallet.publicKey,
      profile: profilePubkey,
      name: playerNamePda,
      systemProgram: web3.SystemProgram.programId,
    },
    signers: [/* include additional signer(s) as required */],
  });
  console.log(`Set profile name to: ${newName}`);
}

──────────────────────────────

4. Detailed Instruction Guide

For each instruction defined in the IDL, we briefly describe the purpose, key accounts, arguments, and provide a TypeScript invocation example.

4.1 acceptRoleInvitation

Purpose:

Enables a new member to accept an invitation to join a role. The invitation status is switched from inactive to active.

Key Accounts:

  • newMember (non‑mutable; not a signer unless it is a wallet)

  • roleAccount (mutable; the role being joined)

  • roleMembershipAccount (mutable; the membership record for the new member)

Arguments:

  • keyIndex (u16)

  • keyIndexInRoleAccount (u16)

  • keyIndexInMembershipAccount (u16)

TypeScript Example:

await program.rpc.acceptRoleInvitation(
  new anchor.BN(keyIndex),
  new anchor.BN(keyIndexInRoleAccount),
  new anchor.BN(keyIndexInMembershipAccount),
  {
    accounts: {
      newMember: newMemberPubkey,
      roleAccount: roleAccountPubkey,
      roleMembershipAccount: roleMembershipPubkey,
    },
  }
);

4.2 addExistingMemberToRole

Purpose:

Adds an existing member to a role via their membership account.

Key Accounts:

  • funder (payer; mutable, signer)

  • newMember (the account of the member being added)

  • profile (the profile associated with the role)

  • roleMembershipAccount (mutable membership account for the member)

  • roleAccount (mutable; the role to add a member to)

  • systemProgram

Arguments:

  • keyIndex (u16)

  • keyIndexInMembershipAccount (u16)

TypeScript Example:

await program.rpc.addExistingMemberToRole(new anchor.BN(keyIndex), new anchor.BN(keyIndexInMembershipAccount), {
  accounts: {
    funder: provider.wallet.publicKey,
    newMember: newMemberPubkey,
    profile: profilePubkey,
    roleMembershipAccount: roleMembershipPubkey,
    roleAccount: roleAccountPubkey,
    systemProgram: web3.SystemProgram.programId,
  },
});

4.3 addKeys

Purpose:

Adds new keys (with non‑auth permissions) to a profile using a key with ADD_KEYS permission.

Key Accounts:

  • funder (mutable; signer)

  • key (signer with ADD_KEYS permission)

  • profile (mutable; profile being updated)

  • systemProgram

  • (Additional remaining accounts: new key accounts to be added)

Arguments:

  • keyAddIndex (u16)

  • keyPermissionsIndex (u16)

  • keysToAdd (Vec<AddKeyInput>)

TypeScript Example:

await program.rpc.addKeys(new anchor.BN(keyAddIndex), new anchor.BN(keyPermissionsIndex), keysToAddArray, {
  accounts: {
    funder: provider.wallet.publicKey,
    key: signerWithAddKeysPermission.publicKey,
    profile: profilePubkey,
    systemProgram: web3.SystemProgram.programId,
  },
  remainingAccounts: additionalKeyAccountMetas,
  signers: [signerWithAddKeysPermission],
});

Note: The AddKeyInput structure must be constructed with fields “scope” (PublicKey), “expireTime” (i64) and “permissions” ([u8;8]).


4.4 adjustAuth

Purpose:

Adjusts the list of authentication keys and threshold. It allows one to add new auth keys and remove existing ones while ensuring the threshold is met.

Key Accounts:

  • funder (mutable; signer)

  • profile (mutable; profile to update)

  • systemProgram

  • (Remaining accounts: auth key accounts and new auth keys)

Arguments:

  • authIndexes (Vec<u16>)

  • newKeyPermissions (Vec<AddKeyInput>)

  • removeRange ([u16; 2])

  • newKeyThreshold (u8)

TypeScript Example:

await program.rpc.adjustAuth(
  authIndexesArray,
  newKeyPermissionsArray,
  removeRangeArray,
  new anchor.BN(newKeyThreshold),
  {
    accounts: {
      funder: provider.wallet.publicKey,
      profile: profilePubkey,
      systemProgram: web3.SystemProgram.programId,
    },
    remainingAccounts: authKeyAccounts, // provide remaining auth accounts
  }
);

4.5 createProfile

Purpose:

Creates a new player profile and initializes its key list based on the provided keys and threshold.

Key Accounts:

  • funder (mutable; payer and signer)

  • profile (mutable; profile being created; must be a signer account)

  • systemProgram

  • (Remaining accounts: key accounts used to initialize the profile)

Arguments:

  • keyPermissions (Vec<AddKeyInput>)

  • keyThreshold (u8)

TypeScript Example:

await program.rpc.createProfile(
  keyPermissionsArray,
  new anchor.BN(keyThreshold),
  {
    accounts: {
      funder: provider.wallet.publicKey,
      profile: profilePubkey, // new profile account pubkey
      systemProgram: web3.SystemProgram.programId,
    },
    remainingAccounts: initialKeyMetas,
    signers: [/* include profile keypair and any other required signers */],
  }
);

4.6 createRole

Purpose:

Creates a new role for the given profile. The role can later be used to manage group-based permissions.

Key Accounts:

  • funder (mutable; signer)

  • profile (mutable; the account that owns the role)

  • newRoleAccount (mutable; role account to be created, derived via a PDA)

  • systemProgram

  • (Remaining accounts: key with CREATE_ROLE permission)

Arguments:

  • keyIndex (u16)

TypeScript Example:

await program.rpc.createRole(new anchor.BN(keyIndex), {
  accounts: {
    funder: provider.wallet.publicKey,
    profile: profilePubkey,
    newRoleAccount: rolePda, // derived PDA for the new role
    systemProgram: web3.SystemProgram.programId,
  },
  remainingAccounts: [createRoleSignerMeta], // include any signing account if needed
  signers: [createRoleSigner],
});

4.7 inviteMemberToRole

Purpose:

Invites a new member to a role by creating a new role membership record in an inactive state.

Key Accounts:

  • funder (mutable; signer)

  • newMember (account of the user to be invited)

  • profile (non‑mutable; profile for the role)

  • roleMembershipAccount (mutable; new membership record account)

  • roleAccount (mutable; the role being joined)

  • systemProgram

Arguments:

  • keyIndex (u16)

TypeScript Example:

await program.rpc.inviteMemberToRole(new anchor.BN(keyIndex), {
  accounts: {
    funder: provider.wallet.publicKey,
    newMember: newMemberPubkey,
    profile: profilePubkey,
    roleMembershipAccount: membershipPda, // derived PDA for role membership
    roleAccount: roleAccountPubkey,
    systemProgram: web3.SystemProgram.programId,
  },
  signers: [provider.wallet.payer],
});

4.8 joinRole

Purpose:

Allows a member to accept an invitation and join a role. This instruction creates a new role membership record and adds the member to the role.

Key Accounts:

  • funder (mutable; signer)

  • newMember (account of the joining member)

  • roleMembershipAccount (mutable; new membership record account)

  • roleAccount (mutable; role account to be updated)

  • systemProgram

Arguments:

  • keyIndex (u16)

TypeScript Example:

await program.rpc.joinRole(new anchor.BN(keyIndex), {
  accounts: {
    funder: provider.wallet.publicKey,
    newMember: newMemberPubkey,
    roleMembershipAccount: membershipPda,
    roleAccount: roleAccountPubkey,
    systemProgram: web3.SystemProgram.programId,
  },
  signers: [provider.wallet.payer],
});

4.9 leaveRole

Purpose:

Permits a member to leave a role. The instruction removes the member from both the role account and the membership account, closing the latter if not needed.

Key Accounts:

  • funder (mutable; receives rent benefits)

  • member (the leaving member’s account)

  • roleMembershipAccount (mutable; membership record to update/close)

  • roleAccount (mutable; role account from which the member is removed)

  • systemProgram

Arguments:

  • keyIndex (u16)

  • keyIndexInRoleAccount (u16)

  • keyIndexInMembershipAccount (u16)

TypeScript Example:

await program.rpc.leaveRole(
  new anchor.BN(keyIndex),
  new anchor.BN(keyIndexInRoleAccount),
  new anchor.BN(keyIndexInMembershipAccount),
  {
    accounts: {
      funder: provider.wallet.publicKey,
      member: memberPubkey,
      roleMembershipAccount: membershipPda,
      roleAccount: roleAccountPubkey,
      systemProgram: web3.SystemProgram.programId,
    },
    signers: [/* member must sign if it’s a wallet */],
  }
);

4.10 removeKeys

Purpose:

Removes non‑auth keys from a profile. It verifies that keys being removed are not the signer themselves and that auth keys cannot be removed using this instruction.

Key Accounts:

  • funder (mutable; receiver of reclaimed rent)

  • key (signer with REMOVE_KEYS permission)

  • profile (mutable; profile to update)

  • systemProgram

  • (No extra remaining accounts unless used for additional key checks)

Arguments:

  • keyIndex (u16)

  • keysToRemove (array [u16; 2]) – the start and end indexes of keys to be removed

TypeScript Example:

await program.rpc.removeKeys(
  new anchor.BN(keyIndex),
  [new anchor.BN(startIndex), new anchor.BN(endIndex)],
  {
    accounts: {
      funder: provider.wallet.publicKey,
      key: signerWithRemoveKeys.publicKey,
      profile: profilePubkey,
      systemProgram: web3.SystemProgram.programId,
    },
    signers: [signerWithRemoveKeys],
  }
);

4.11 removeMemberFromRole

Purpose:

Removes a member from a role—this is initiated by an authorized key. Both the role’s membership list and the member’s role membership account are updated.

Key Accounts:

  • funder (mutable; receives rent benefits)

  • member (the member being removed)

  • profile (non‑mutable; required for validation)

  • roleMembershipAccount (mutable; membership record to update)

  • roleAccount (mutable; role from which the member is removed)

  • systemProgram

Arguments:

  • keyIndex (u16)

  • keyIndexInRoleAccount (u16)

  • keyIndexInMembershipAccount (u16)

TypeScript Example:

await program.rpc.removeMemberFromRole(
  new anchor.BN(keyIndex),
  new anchor.BN(keyIndexInRoleAccount),
  new anchor.BN(keyIndexInMembershipAccount),
  {
    accounts: {
      funder: provider.wallet.publicKey,
      member: memberPubkey,
      profile: profilePubkey,
      roleMembershipAccount: membershipPda,
      roleAccount: roleAccountPubkey,
      systemProgram: web3.SystemProgram.programId,
    },
  }
);

4.12 removeRole

Purpose:

Removes a role from a profile. The instruction will close the role (and its name account if it exists) provided the role has no active members.

Key Accounts:

  • funder (mutable; receives reclaimed rent)

  • profile (mutable; associated profile)

  • roleAccount (mutable; role to be removed)

  • roleNameAccount (mutable; optional account storing the role’s name; closed upon removal)

Arguments:

  • roleNameBump (u8)

  • keyIndex (u16)

TypeScript Example:

await program.rpc.removeRole(new anchor.BN(roleNameBump), new anchor.BN(keyIndex), {
  accounts: {
    funder: provider.wallet.publicKey,
    profile: profilePubkey,
    roleAccount: roleAccountPubkey,
    roleNameAccount: roleNamePda, // derived using roleAccount key and bump
  },
});

4.13 setName

Purpose:

Sets or updates the profile’s name using a designated name account. The instruction may initialize the account if needed.

Key Accounts:

  • key (signer with CHANGE_NAME permission)

  • funder (mutable; pays for rent changes)

  • profile (non‑mutable; the profile)

  • name (mutable; the name account which is either pre‑initialized or created on‑the‑fly)

  • systemProgram

Arguments:

  • keyIndex (u16)

  • name (bytes – typically a UTF‑8 encoded string)

TypeScript Example:

await program.rpc.setName(new anchor.BN(keyIndex), Buffer.from("Hello!"), {
  accounts: {
    key: signerWithChangeName.publicKey,
    funder: provider.wallet.publicKey,
    profile: profilePubkey,
    name: playerNamePda, // derived as shown in the PDA section
    systemProgram: web3.SystemProgram.programId,
  },
  signers: [signerWithChangeName],
});

4.14 setRoleAcceptingMembers

Purpose:

Flags the role to accept new members.

Key Accounts:

  • profile (non‑mutable)

  • roleAccount (mutable; role to update)

Arguments:

  • keyIndex (u16)

TypeScript Example:

await program.rpc.setRoleAcceptingMembers(new anchor.BN(keyIndex), {
  accounts: {
    profile: profilePubkey,
    roleAccount: roleAccountPubkey,
  },
});

4.15 setRoleAuthorizer

Purpose:

Sets the authorizer field on a role if it has not already been set.

Key Accounts:

  • funder (mutable; signer)

  • profile (non‑mutable)

  • roleAccount (mutable; role to be updated)

  • authorizer (non‑mutable; account to be assigned as authorizer)

Arguments:

  • keyIndex (u16)

TypeScript Example:

await program.rpc.setRoleAuthorizer(new anchor.BN(keyIndex), {
  accounts: {
    funder: provider.wallet.publicKey,
    profile: profilePubkey,
    roleAccount: roleAccountPubkey,
    authorizer: authorizerPubkey,
  },
  signers: [provider.wallet.payer],
});

4.16 setRoleName

Purpose:

Sets or updates the name of a role using a separate name account.

Key Accounts:

  • funder (mutable; signer)

  • profile (non‑mutable; profile owning the role)

  • role (non‑mutable; the role to update name for)

  • name (mutable; name account holding the role name)

  • systemProgram

Arguments:

  • keyIndex (u16)

  • name (bytes – role name as UTF‑8 encoded string)

TypeScript Example:

await program.rpc.setRoleName(new anchor.BN(keyIndex), Buffer.from("Commander"), {
  accounts: {
    funder: provider.wallet.publicKey,
    profile: profilePubkey,
    role: roleAccountPubkey,
    name: roleNamePda, // derived PDA for role name account
    systemProgram: web3.SystemProgram.programId,
  },
  signers: [provider.wallet.payer],
});

4.17 setRoleNotAcceptingMembers

Purpose:

Flags a role so that it stops accepting new members.

Key Accounts:

  • profile (non‑mutable)

  • roleAccount (mutable; role to update)

Arguments:

  • keyIndex (u16)

TypeScript Example:

await program.rpc.setRoleNotAcceptingMembers(new anchor.BN(keyIndex), {
  accounts: {
    profile: profilePubkey,
    roleAccount: roleAccountPubkey,
  },
});

──────────────────────────────

5. Key Accounts & Structures

Below are the most important data structures along with definitions of their fields.

Profile Account

Purpose:

Stores the core profile information including version, key threshold, authentication key count, next role sequence ID, and creation timestamp.

Data Fields:

  • version: u8 – Data version.

  • authKeyCount: u16 – Number of authentication keys.

  • keyThreshold: u8 – Minimum number of auth keys required for sensitive operations.

  • nextSeqId: u64 – Sequence counter for new roles.

  • createdAt: i64 – Unix timestamp of creation.


PlayerName Account

Purpose:

Holds a human‑readable name for a profile or role.

Data Fields:

  • version: u8 – Data version.

  • profile: PublicKey – The profile’s public key this name is associated with.

  • bump: u8 – PDA bump seed.

PDA Derivation:

const [playerNamePda, bump] = await web3.PublicKey.findProgramAddress(
  [Buffer.from("player_name"), profilePubkey.toBuffer()],
  program.programId
);

ProfileRoleMembership Account

Purpose:

Manages a player’s membership to various roles.

Data Fields:

  • version: u8 – Data version.

  • profile: PublicKey – Profile identifier.

  • member: PublicKey – Member’s profile public key.

  • bump: u8 – PDA bump seed.

PDA Derivation:

Typically derived from seeds such as ["role-member", profilePubkey, memberPubkey].


Role Account

Purpose:

Represents a role that is associated with a profile. It maintains an unordered list of role membership entries in its remaining data.

Data Fields:

  • version: u8 – Data version.

  • profile: PublicKey – The owning profile.

  • authorizer: PublicKey – (Optional) Authority for role modifications.

  • roleSeqId: u64 – Sequence identifier for the role.

  • acceptingNewMembers: u8 – Boolean indicator (1 = true, 0 = false).

  • bump: u8 – PDA bump seed.

PDA Derivation Example:

const [rolePda, roleBump] = await web3.PublicKey.findProgramAddress(
  [
    Buffer.from("profile-role"),
    profilePubkey.toBuffer(),
    new anchor.BN(roleSeqId).toArrayLike(Buffer, "le", 8)
  ],
  program.programId
);

ProfileKey Structure

Purpose:

Defines an individual key entry along with its permissions, scope, and expiry information.

Data Fields:

  • key: PublicKey – The public key.

  • scope: PublicKey – Defines the operational scope.

  • expireTime: i64 – Expiration time (non‑expiring if negative).

  • permissions: [u8;8] – Bitflag‐encoded permissions.


RoleMembership Structure

Purpose:

Stores a member’s status in a role.

Data Fields:

  • key: PublicKey – The key/identifier of the member.

  • status: u8 – Membership status (0 = Inactive, 1 = Active).

──────────────────────────────

6. Program Errors

Below is a complete table of error codes, names, and messages as defined in the program:

Error Code
Name
Message

6000

KeyIndexOutOfBounds

Key index out of bounds

6001

ProfileMismatch

Profile did not match profile key

6002

KeyMismatch

Key did not match profile key

6003

ScopeMismatch

Scope did not match profile scope

6004

KeyExpired

Key expired

6005

KeyMissingPermissions

Key is missing permissions

6006

PermissionsMismatch

Permissions don't match available

6007

AuthKeyCannotExpire

Auth keys cannot expire

6008

AuthKeyMustSign

New auth keys must be signers

6009

DuplicateAuthKey

Duplicate key when adjusting auth keys

6010

RoleAuthorityAlreadySet

Role authority has already been set

6011

RoleNotAcceptingMembers

Role is not accepting new members

6012

RoleMembershipMismatch

Role membership is not as expected

6013

RoleLimitExceeded

Role limit exceeded

6014

RoleHasMembers

Cannot remove role with members

6015

FeatureNotImplemented

This feature is not yet support

──────────────────────────────

7. Key Constants

Some of the important constants include:

  • MAX_MEMBERSHIPS: 256

    Significance: Maximum members allowed per role.

  • Permission Bitflags (ProfilePermissions):

    Permissions such as AUTH (1 << 0), ADD_KEYS (1 << 1), REMOVE_KEYS (1 << 2), CHANGE_NAME (1 << 3), etc.

    Significance: These define what actions a key may perform relative to a profile or role.

──────────────────────────────

8. PDAs (Program Derived Addresses)

Many accounts in the player_profile program are derived using PDAs. Below are sample derivations:

8.1 Deriving PlayerName PDA

Seed Values:

  • Literal: "player_name"

  • Profile public key

Sample Code:

const [playerNamePda, bump] = await web3.PublicKey.findProgramAddress(
  [Buffer.from("player_name"), profilePubkey.toBuffer()],
  program.programId
);

8.2 Deriving Role PDA

Seed Values:

  • Literal: "profile-role"

  • Profile public key

  • Role sequence ID (as little‑endian byte array)

Sample Code:

const roleSeqId = 0; // or the current value from the profile account
const [rolePda, roleBump] = await web3.PublicKey.findProgramAddress(
  [
    Buffer.from("profile-role"),
    profilePubkey.toBuffer(),
    new anchor.BN(roleSeqId).toArrayLike(Buffer, "le", 8)
  ],
  program.programId
);

8.3 Deriving RoleMembership PDA

Often derived with a combination of a discriminator (e.g., "role-member"), the profile key, and the member key. Refer to the program’s seed definitions for details.

──────────────────────────────

9. Final Remarks

  • Helpful Hints:

    • Use the provided TypeScript snippets as a starting point for your integration.

    • Make use of the error table for debugging any on‑chain issues that arise during development.

This documentation should help you quickly integrate and interact with the player_profile program using its IDL and standard Anchor-based workflows. For any further clarifications, refer to the attached IDL (player_profile.json) or check additional inline comments in the program source.

Last updated

Was this helpful?