Skip to content

Player Profile

@staratlas/dev-player-profile is the identity and authorization foundation used by SAGE C4.

In Star Atlas terms, this is the player identity layer. A wallet signs, but a profile is the durable account other programs use to decide which player, name, role, and delegated authority is acting.

For SAGE C4 developers, Player Profile is the first account model to understand before fleets, crafting, cargo, claim stakes, or markets. Most player-facing SAGE instructions validate a profile and a keyIndex before they mutate game state.

Developer use

Use this page when you need to:

  • resolve a Star Atlas profile from application context
  • read a player name or profile role membership
  • explain which profile key is authorizing a SAGE instruction
  • check whether a profile key is present before building a transaction
  • prepare profile-validation accounts for SAGE instructions

Technical model

The package is Codama-generated, depends on @solana/program-client-core, and uses @solana/kit ^6.1.0 as a peer dependency.

Package metadata

FieldValue
Package@staratlas/dev-player-profile
Version0.45.7
LicenseApache-2.0
GeneratorCodama-generated Solana client
Dependency@solana/program-client-core ^6.1.0
Peer dependency@solana/kit ^6.1.0

Program address

ts
import { PLAYER_PROFILE_PROGRAM_ADDRESS } from '@staratlas/dev-player-profile';

console.log(PLAYER_PROFILE_PROGRAM_ADDRESS);
// C4PRoFNroxxzdgeCoM31LJjYRg7kT6ymogSTAT99iD1u

What it owns

Player Profile provides the account model for Star Atlas player identity and authorization:

  • profile accounts
  • player names
  • role accounts
  • profile-role memberships
  • profile keys and permission flags

SAGE instructions commonly need profile validation accounts or profile-related authority checks. Treat this package as the first foundation layer before documenting fleet, cargo, crafting, combat, or claim-stake flows.

Ecosystem role

In game terms, Player Profile is not a SAGE gameplay loop by itself. It is the player identity layer that lets other Star Atlas programs reason about who owns or controls game state.

This page covers:

  • which wallet or authority is acting for a profile
  • how a player name relates to a profile
  • how profile keys and roles model delegated authority
  • which permissions a SAGE action expects before an instruction is built

Once profile authority is clear, later SAGE pages can focus on the gameplay domain they cover.

Authority model

Most SAGE transaction builders ask for two profile-related values:

  • profileValidationProfile: the Player Profile account being validated
  • keyIndex: the index of the profile key authorizing the action

That means a wallet signature is not enough context for a UI. The UI also needs to show which profile key is being used and whether that key is present, expired, or scoped for the intended action.

The generated profile stores profileKeys as an ordered array. Treat the keyIndex as an account-data index, not a wallet address.

Generated surface

The package exports roughly 347 named symbols.

GroupCountExamples
Accounts4Profile, PlayerName, Role, ProfileRoleMembership
Instructions18createProfile, setName, addKeys, createRole, acceptInvite
PDA helpers3findPlayerNamePda, findRolePda, findProfileRoleMembershipPda
Generated types4ProfileKey, RoleMembership, MemberStatus, AddKeyInput

Account families

Player Profile has four account types.

AccountBeginner meaningKey fields
ProfileThe Star Atlas identity account. This is the account other programs use when they need to know which player/profile is acting.authKeyCount, keyThreshold, nextSeqId, createdAt, profileKeys
PlayerNameThe on-chain display name for a profile.profile, name, bump
RoleA role owned by a profile. Roles can group members and model delegated authority.profile, authorizer, roleSeqId, acceptingNewMembers, memberships
ProfileRoleMembershipThe roles one member profile has within another profile's role system.profile, member, memberships

Each generated account file provides discriminator bytes, codecs, decoders, and fetch helpers.

ts
import {
	fetchMaybeProfile,
	fetchMaybePlayerNameFromSeeds,
	fetchMaybeProfileRoleMembershipFromSeeds,
	fetchMaybeRoleFromSeeds
} from '@staratlas/dev-player-profile';

Use the fetchMaybe... variants first. Missing accounts are common while exploring, and exists: false is easier for beginners than a thrown error.

Account fields

Profile

Profile is the core identity account.

txt
version       data version
authKeyCount  number of auth keys on the profile
keyThreshold  number of auth keys needed to update profile auth
nextSeqId     next sequence number for a new role
createdAt     creation timestamp
profileKeys   keys, scopes, expiry times, and permission bytes

The profile address is not automatically the wallet address. A wallet signs; a profile stores Star Atlas identity and permissions.

PlayerName

PlayerName stores the readable name for a profile. It is derived from the profile address.

ts
const playerName = await fetchMaybePlayerNameFromSeeds(
	rpc,
	{ profile: profileAddress },
	{ commitment: 'confirmed' }
);

The generated account stores name as bytes. Decode those bytes at the edge of your app:

ts
const name = new TextDecoder().decode(new Uint8Array(playerName.data.name));

Role

Role belongs to a profile and has a sequence id. It can accept members and stores unordered membership entries.

Use fetchMaybeRoleFromSeeds when you know the owning profile and role sequence id:

ts
const role = await fetchMaybeRoleFromSeeds(
	rpc,
	{
		profile: profileAddress,
		roleSeqId: 0n
	},
	{ commitment: 'confirmed' }
);

ProfileRoleMembership

ProfileRoleMembership links a member profile to roles on another profile.

ts
const membership = await fetchMaybeProfileRoleMembershipFromSeeds(
	rpc,
	{
		profile: profileAddress,
		member: memberProfileAddress
	},
	{ commitment: 'confirmed' }
);

Read it as: "which roles does this member have on this profile?"

PDA helpers

A PDA is an account address derived from known inputs. Player Profile exposes three PDA helpers.

HelperSeedsMeaning
findPlayerNamePdaprofileFind the player-name account for a profile.
findRolePdaprofile, roleSeqIdFind one role for a profile.
findProfileRoleMembershipPdaprofile, memberFind one member's role membership account for a profile.

The seed prefixes are generated into the package. Most application code should call the helpers instead of rebuilding seeds manually.

ts
import { findPlayerNamePda } from '@staratlas/dev-player-profile';

const [playerNameAddress] = await findPlayerNamePda({
	profile: profileAddress
});

Generated types

TypeMeaning
ProfileKeyOne key on a profile, with a key, permission scope, expireTime, and raw permission bytes.
AddKeyInputInput shape used when adding a key. It has scope, expireTime, and permission bytes.
RoleMembershipA member key plus membership status.
MemberStatusEnum with Inactive and Active.

expireTime < 0 means the key does not expire. Auth keys cannot expire.

Instruction families

The generated package exposes instruction builders named getXInstruction, and parsers named parseXInstruction.

Read-only examples should come first. These instruction builders create transaction instructions, but they do not send them by themselves.

FamilyInstructionsWhat they manage
Profile creationcreateProfileCreate a profile account with initial key permissions and threshold.
NamesetNameCreate or resize the player-name account and set profile name bytes.
Keys and authaddKeys, removeKeys, adjustAuthAdd non-auth keys, remove non-auth keys, and adjust auth keys or auth threshold.
RolescreateRole, removeRole, setRoleName, setRoleAuthorizer, setRoleAcceptingMembers, setRoleNotAcceptingMembersCreate, remove, rename, authorize, and open or close roles.
MembershipinviteMember, acceptInvite, joinRole, leaveRole, addRoleMember, removeRoleMemberInvite, accept, join, leave, add, or remove members from roles.
SOL vaultdrainSolVaultWithdraw from the profile SOL vault. Treat this as highly sensitive.

Beginner-safe read example

This example reads a profile and its player name. It does not sign or send a transaction.

ts
import { address, createSolanaRpc } from '@solana/kit';
import {
	fetchMaybePlayerNameFromSeeds,
	fetchMaybeProfile
} from '@staratlas/dev-player-profile';

const rpc = createSolanaRpc('https://testnet-rpc.z.ink');
const profileAddress = address('PROFILE_ADDRESS_HERE');

const profile = await fetchMaybeProfile(rpc, profileAddress, {
	commitment: 'confirmed'
});

if (!profile.exists) {
	throw new Error(`Profile not found: ${profileAddress}`);
}

const playerName = await fetchMaybePlayerNameFromSeeds(
	rpc,
	{ profile: profileAddress },
	{ commitment: 'confirmed' }
);

console.log({
	authKeyCount: profile.data.authKeyCount,
	keyThreshold: profile.data.keyThreshold,
	name: playerName.exists
		? new TextDecoder().decode(new Uint8Array(playerName.data.name))
		: null
});

Review a profile key

This example checks the key that a later SAGE instruction would reference by keyIndex.

ts
import { address, createSolanaRpc } from '@solana/kit';
import { fetchMaybeProfile } from '@staratlas/dev-player-profile';

const rpc = createSolanaRpc('https://testnet-rpc.z.ink');
const profileAddress = address('PROFILE_ADDRESS_HERE');
const keyIndex = 0;

const profile = await fetchMaybeProfile(rpc, profileAddress, {
	commitment: 'confirmed'
});

if (!profile.exists) {
	throw new Error(`Profile not found: ${profileAddress}`);
}

const profileKey = profile.data.profileKeys[keyIndex];

if (!profileKey) {
	throw new Error(`Profile key index ${keyIndex} does not exist`);
}

const now = BigInt(Math.floor(Date.now() / 1000));
const neverExpires = profileKey.expireTime < 0n;
const expired = !neverExpires && profileKey.expireTime <= now;

console.log({
	key: profileKey.key,
	scope: profileKey.scope,
	expireTime: profileKey.expireTime,
	expired,
	permissionBytes: profileKey.permissions
});

Use this kind of check before showing a SAGE instruction. It gives the user a concrete authority review instead of a vague "connect wallet" prompt.

Instruction example

This example builds a setName instruction object for review. It still needs transaction summary and simulation before a wallet prompt.

ts
import { address } from '@solana/kit';
import {
	findPlayerNamePda,
	getSetNameInstruction
} from '@staratlas/dev-player-profile';

const profileAddress = address('PROFILE_ADDRESS_HERE');
const [nameAddress] = await findPlayerNamePda({ profile: profileAddress });
const encodedName = Array.from(new TextEncoder().encode('My SAGE Name'));

// These signers must come from your wallet/signing flow.
// They are placeholders here so the example stays in review mode.
const instruction = getSetNameInstruction({
	key: keySigner,
	funder: funderSigner,
	profile: profileAddress,
	name: nameAddress,
	keyIndex: 0,
	nameArg: encodedName,
	remainingAccounts: []
});

console.log(instruction.programAddress);

Before sending a transaction built from this instruction, explain which key signs, why keyIndex points to that key, who pays rent or resize costs, and what account will change.

Permissions

The package exports ProfilePermissions, PROFILE_FLAGS, and BitFlags.

ts
import { PROFILE_FLAGS, ProfilePermissions } from '@staratlas/dev-player-profile';

const allProfilePermissions = ProfilePermissions.all();
const canAddKeys = new ProfilePermissions(PROFILE_FLAGS.ADD_KEYS);

The current permission flags are:

FlagMeaning
AUTHAuth key that can update the profile. Auth permission changes must meet the threshold.
ADD_KEYSAdd non-auth keys.
REMOVE_KEYSRemove non-auth keys.
CHANGE_NAMEChange the profile's player name.
CREATE_ROLECreate a new role.
REMOVE_ROLERemove a role.
SET_AUTHORIZERSet a role's authorizer.
JOIN_ROLEJoin a role.
LEAVE_ROLELeave a role.
TOGGLE_ACCEPTING_NEW_MEMBERSOpen or close a role to new members.
ADD_MEMBERAdd a member to a role.
REMOVE_MEMBERRemove a member from a role.
DRAIN_SOL_VAULTWithdraw from the SOL vault.

Profile permissions are security-sensitive. Any example that changes keys, roles, memberships, authorizers, or authentication settings must show the account metas plainly and recommend simulation before signing.

Errors

The package exports Player Profile error constants and helpers:

ts
import {
	getPlayerProfileErrorMessage,
	isPlayerProfileError,
	PLAYER_PROFILE_ERROR__KEY_MISSING_PERMISSIONS
} from '@staratlas/dev-player-profile';

Common beginner-facing errors include:

ErrorMeaning
KEY_INDEX_OUT_OF_BOUNDS / KEY_INDEX_NOT_SETThe instruction referenced a profile key index that does not exist or is empty.
KEY_MISMATCH / PROFILE_MISMATCH / SCOPE_MISMATCHThe provided accounts do not match the key, profile, or scope expected by the profile data.
KEY_EXPIREDThe selected key has expired.
KEY_MISSING_PERMISSIONSThe selected key does not have the required permission flag.
INVALID_KEY_THRESHOLD / TOO_FEW_AUTH_KEYSThe auth threshold is invalid for the auth keys supplied.
ROLE_NOT_ACCEPTING_MEMBERSThe role is closed to new members.
ROLE_HAS_MEMBERSThe role cannot be removed while members remain.

Common Starting Points

Start with these pages before deeper SAGE examples:

  • Create or fetch a profile.
  • Resolve a player name.
  • Add and remove profile keys.
  • Create a role and invite a member.
  • Understand profile permissions used by SAGE.

Once those are clear, SAGE pages can reference profile validation without re-explaining the identity model each time.

Star Atlas Identity Layer

Player Profile is shared identity and authorization infrastructure across Star Atlas Solana programs. SAGE uses it to validate which profile is acting and which profile key is authorizing a generated instruction.