@sickart/portrait
v1.0.0
Published
Resolve and set on-chain profiles for Solana wallets. Like Gravatar, but trustless.
Downloads
15
Maintainers
Readme
@sickart/portrait
TypeScript client for Portrait — on-chain profiles for Solana wallets. Set an NFT as your wallet's avatar; any app can resolve it. Like Gravatar, but trustless.
npm install @sickart/portrait @solana/kitQuick start
import { createSolanaRpc, address } from '@solana/kit';
import { resolveProfile } from '@sickart/portrait';
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const wallet = address('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM');
const profile = await resolveProfile(rpc, wallet);
// { owner, avatar, image, verified, updatedAt } | null
if (profile?.image) {
console.log(profile.image, profile.verified ? '✓' : '?');
}API
Readers
getProfile(rpc, wallet): Promise<Profile | null>Cheap on-chain read. Returns the stored avatar mint and updated time. Does
not walk Metaplex metadata or check ownership; image and verified are
always null.
resolveProfile(rpc, wallet): Promise<Profile | null>Full resolution. Walks Metaplex metadata for the avatar mint, fetches the off-chain JSON, and verifies the wallet still holds the NFT. The off-chain fetch is SSRF-hardened (DNS pinning, scheme allowlist, response-size cap, redirect rejection).
resolveProfiles(rpc, wallets[]): Promise<Profile[]>Batch resolve. Wallets without a profile are omitted from the result.
Writers
getSetAvatarInstruction({ owner, avatar }): Promise<IInstruction>
getClearAvatarInstruction({ owner }): Promise<IInstruction>Return unsigned instructions. Compose into a transaction with your own fee-payer / signer / commitment / priority-fee logic.
Utilities
deriveProfilePda(wallet): Promise<Address> // [b"profile", wallet]
PROGRAM_ID // the deployed program addressTypes
type Profile = {
owner: Address;
avatar: Address; // mint address of the NFT used as avatar
image: string | null; // resolved image URL (resolveProfile only)
verified: boolean | null; // true if wallet still holds the avatar mint
updatedAt: number; // unix seconds
};verified semantics: true = wallet currently holds the NFT, false =
doesn't hold it (cosplay), null = couldn't check (RPC failure). Render UI
accordingly — show ✓ for true, ? for null, hide or dim for false.
Setting an avatar (browser)
import { getSetAvatarInstruction } from '@sickart/portrait';
const ix = await getSetAvatarInstruction({
owner: signer.address,
avatar: chosenNftMint,
});
// hand `ix` to your tx builder + wallet.sendTransactionLicense
MIT
