3) iiprofile – Portable On-Chain Profile + Key-Value Storage for II [Mainnet]

Introduction

Every ICP app that wants to show a user profile asks the same questions: What’s your name? Upload a photo. Add your bio. Social links?

Users answer the same questions on every app they use. That data lives in isolated app databases — inaccessible to other apps, owned by the platform instead of the user, and reset every time the user tries a new dApp.

iiprofile changes this. It’s a portable, decentralized key-value store built on the iilink principal layer. Write your profile once. Every iilink-integrated app reads the same data — automatically, with no re-entry, no per-app setup, and no centralized data custodian.

It’s live on ICP mainnet. I’m shipping it alongside iilink and iiname as the third layer of a portable ICP identity stack.


The Problem

The ICP ecosystem has a data portability problem. Because every app sees a different principal per user (the root problem iilink solves at the identity layer), there’s also no practical way to share user data between apps. Even if two apps wanted to share profile data, they’d have no common user identifier to key it on.

iilink solves the identity layer. iiprofile solves the data layer.

With a unified main principal from iilink, it’s now possible to attach persistent, user-owned data to that principal — and have every app in the ecosystem read from the same source of truth.


The Solution: iiprofile

iiprofile is an on-chain key-value store where:

  • ->Storage is rented annually per iilink main principal

  • ->Only the principal owner can write (authenticated via Internet Identity + iilink proxy auth)

  • ->Anyone can read — apps make anonymous read calls with the user’s principal

  • ->Keys follow an open naming convention: iiprofile: for standard profile fields, yourapp: for app-specific data

  • ->The same canister stores everything — one round-trip fetches a complete profile


Key Features

For users:

  • ->One profile setup covers every iilink-integrated app going forward

  • ->Free public profile page at /@yourname (if you have an iiname) or via principal

  • ->Store name, bio, avatar, banner, CTA button, social handles, up to 5 custom links

  • ->Full control — only you can write to your store; revoke nothing, because apps only read

For developers:

  • ->Fetch a complete user profile in one batched call — no auth required

  • ->Write app-specific keys to the same store (yourapp:theme, yourapp:preferences, etc.)

  • ->Open key convention: define your own namespace, document it, other apps can interoperate

  • ->The key-value model is intentionally simple — no schema enforcement


Storage Plans

All plans are annual (365 days), payable in ICP or TCYCLES:

Plan TCYCLES / year Storage Max Keys Writes / day
Starter 0.1 64 KiB 128 50
Builder 0.5 256 KiB 512 300
Power 2.0 1 MiB 1,024 500

Write quotas refill daily. Unused writes don’t carry over. Plans can be upgraded at any time with data preserved.


Standard Profile Keys

The iiprofile:* namespace is an open convention — not enforced by the canister. Any app can read these keys. Any app can extend the convention with new keys.

iiprofile:name              iiprofile:bio
iiprofile:photo_url         iiprofile:banner_photo_url
iiprofile:background_photo_url
iiprofile:twitter           iiprofile:github
iiprofile:discord           iiprofile:telegram
iiprofile:email             iiprofile:website
iiprofile:cta_text          iiprofile:cta_button_text
iiprofile:cta_url           iiprofile:cta_photo_url
iiprofile:link1_text        iiprofile:link1_url
iiprofile:link2_text        iiprofile:link2_url
iiprofile:link3_text        iiprofile:link3_url
iiprofile:link4_text        iiprofile:link4_url
iiprofile:link5_text        iiprofile:link5_url

Developer Integration

Read a Full Profile (Anonymous, One Call)

// main = { owner: Principal, subaccount: [] }
// This is the user's iilink main principal — you get it from the iilink auth flow

const args = [
  { main, key: 'iiprofile:name' },
  { main, key: 'iiprofile:bio' },
  { main, key: 'iiprofile:photo_url' },
  { main, key: 'iiprofile:twitter' },
  { main, key: 'iiprofile:website' },
  { main, key: 'iiprofile:cta_text' },
  { main, key: 'iiprofile:cta_url' },
  // ... include all keys you need, up to ~25 in one batch
];

const res = await iiprofile.akvs_values(args);

// Build a profile object from results
const profile = {};
res.results.forEach((opt, i) => {
  if (opt[0]?.Text) profile[args[i].key] = opt[0].Text;
});

// profile['iiprofile:name']    → 'Kay'
// profile['iiprofile:twitter'] → '@kayicp'
// profile['iiprofile:photo_url'] → 'https://...'

Write Your App-Specific Keys

I will implement opening iiprofile popup for your user to write your app keyvalues. Soon™.


The Open Key Convention

One design decision worth highlighting: the iiprofile:* keys are a convention, not a permission system. The canister doesn’t enforce which keys mean what. This is intentional.

Any app can:

  1. Read standard iiprofile:* keys to show a user’s profile

  2. Write to its own yourapp:* namespace for preferences/settings

  3. Publish their key definitions so other apps can interoperate

This creates a composable data layer where each new app that defines useful keys makes the entire ecosystem more valuable. A user’s iiprofile storage gradually becomes a portable data record that follows them everywhere.


Current State & Limitations

  • ->Annual renewal required — there’s no auto-renew mechanism. An on-chain reminder system is planned.

  • ->Early stage — I’m the first user. The canister is live and functional but the ecosystem of apps reading from it is just getting started.


Relationship to iilink and iiname

These three projects are designed to work together:

  • ->iilink provides the unified principal — the common identifier that makes cross-app data portable

  • ->iiname makes that principal human-readable — giving users an @handlehandlehandlehandle across the ecosystem

  • ->iiprofile attaches persistent, portable data to that principal — profile, preferences, and app-specific settings

Each is independently useful, but together they form a complete portable identity layer for ICP.


Call to Action

If you’re building any app that involves user profiles, display names, or per-user settings, I’d love for you to consider iiprofile as your data layer instead of building it from scratch.

The integration path:

  1. Get the user’s iilink main principal from the iilink auth flow

  2. Call akvs_values() with the keys you need — no auth required for reads

  3. Display the result; fall back gracefully if the user hasn’t set up iiprofile yet

Try it: https://kvya4-zaaaa-aaaac-qgtea-cai.icp0.io/

Backend canister: lywes-wiaaa-aaaac-qgtdq-cai (dashboard)

My iiprofile:

https://kvya4-zaaaa-aaaac-qgtea-cai.icp0.io/@e6agv-wen3n-4a4y3-4zpeu-47coa-kso4c-vvow5-p5fbv-2pjg4-bm53l-lqe

or replace the principal with my iiname:

https://kvya4-zaaaa-aaaac-qgtea-cai.icp0.io/@kayicp

Feedback welcome.

previous topics:

  1. iilink
  2. iiname

byebye

3 Likes