Universal Profile
Manage LUKSO Universal Profiles — identity, permissions, tokens, blockchain operations. Cross-chain support for Base and Ethereum.
Manage LUKSO Universal Profiles — identity, permissions, tokens, blockchain operations. Cross-chain support for Base and Ethereum.
Real data. Real impact.
Growing
Developers
Per week
Open source
Skills give you superpowers. Install in 30 seconds.
Authorize your bot: create a profile at my.universalprofile.cloud, generate a controller key, authorize via Authorization UI.
execute() so msg.sender = UP address.setData()/setDataBatch() can be called directly on UP (checks permissions internally).Controller → UP.execute(operation, target, value, data) → Target
The controller calls
execute() directly on the UP contract. The UP internally verifies permissions via its KeyManager (LSP20 lsp20VerifyCall). Do NOT call the KeyManager's execute() function directly. Always call the UP.
Controller signs LSP25 → Relay API submits → KeyManager.executeRelayCall() → UP
The controller signs a message, then the LUKSO relay service submits the transaction. Do NOT call executeRelayCall() yourself — the relay API does this.
⚠️ CRITICAL: The relay/gasless option exists ONLY on LUKSO mainnet (42) and testnet (4201). On Base, Ethereum, and all other chains, the controller must hold native ETH and pay gas directly. There is no gasless alternative.
Typical gas costs: LUKSO ~free via relay, Base ~$0.001-0.01/tx, Ethereum ~$0.10-1.00/tx.
| Chain | ID | RPC | Explorer | Relay | Token |
|---|---|---|---|---|---|
| LUKSO | 42 | | | | LYX |
| LUKSO Testnet | 4201 | | | | LYXt |
| Base | 8453 | | | ❌ | ETH |
| Ethereum | 1 | | | ❌ | ETH |
up status # Config, keys, connectivity up profile info [<address>] [--chain <chain>] # Profile details up profile configure <address> [--chain lukso] # Save UP for use up key generate [--save] [--password <pw>] # Generate controller keypair up permissions encode <perm1> [<perm2> ...] # Encode to bytes32 up permissions decode <hex> # Decode to names up permissions presets # List presets up authorize url [--permissions <preset|hex>] # Generate auth URL up quota # Check relay gas quota (LUKSO only)
Presets:
read-only 🟢 | token-operator 🟡 | nft-trader 🟡 | defi-trader 🟠 | profile-manager 🟡 | full-access 🔴
Config lookup order:
UP_CREDENTIALS_PATH env → ~/.openclaw/universal-profile/config.json → ~/.clawdbot/universal-profile/config.json
Key lookup order:
UP_KEY_PATH env → ~/.openclaw/credentials/universal-profile-key.json → ~/.clawdbot/credentials/universal-profile-key.json
Canonical path for new credentials:
~/.openclaw/credentials/universal-profile-key.json
Skill config path:
~/.openclaw/skills/universal-profile/config.json
Expected JSON format:
{ "universalProfile": { "address": "0xYourUniversalProfileAddress" }, "controller": { "address": "0xYourControllerAddress", "privateKey": "0xYourPrivateKey" } }
Key file permissions:
chmod 600. Keys loaded only for signing, then cleared. The skill warns if credential files are readable by group/others.
| Permission | Hex | Risk | Notes |
|---|---|---|---|
| CHANGEOWNER | 0x01 | 🔴 | |
| ADDCONTROLLER | 0x02 | 🟠 | |
| EDITPERMISSIONS | 0x04 | 🟠 | |
| ADDEXTENSIONS | 0x08 | 🟡 | |
| CHANGEEXTENSIONS | 0x10 | 🟡 | |
| ADDUNIVERSALRECEIVERDELEGATE | 0x20 | 🟡 | |
| CHANGEUNIVERSALRECEIVERDELEGATE | 0x40 | 🟡 | |
| REENTRANCY | 0x80 | 🟡 | |
| SUPER_TRANSFERVALUE | 0x0100 | 🟠 | Any recipient |
| TRANSFERVALUE | 0x0200 | 🟡 | AllowedCalls only |
| SUPER_CALL | 0x0400 | 🟠 | Any contract |
| CALL | 0x0800 | 🟡 | AllowedCalls only |
| SUPER_STATICCALL | 0x1000 | 🟢 | |
| STATICCALL | 0x2000 | 🟢 | |
| SUPER_DELEGATECALL | 0x4000 | 🔴 | |
| DELEGATECALL | 0x8000 | 🔴 | |
| DEPLOY | 0x010000 | 🟡 | |
| SUPER_SETDATA | 0x020000 | 🟠 | Any key |
| SETDATA | 0x040000 | 🟡 | AllowedERC725YDataKeys only |
| ENCRYPT | 0x080000 | 🟢 | |
| DECRYPT | 0x100000 | 🟢 | |
| SIGN | 0x200000 | 🟢 | |
| EXECUTE_RELAY_CALL | 0x400000 | 🟢 |
SUPER variants = unrestricted. Regular = restricted to AllowedCalls/AllowedERC725YDataKeys. Prefer restricted.
// Controller calls UP.execute() directly — works on LUKSO, Base, Ethereum const provider = new ethers.JsonRpcProvider(rpcUrl); // use correct RPC for chain const wallet = new ethers.Wallet(controllerPrivateKey, provider); const up = new ethers.Contract(upAddress, ['function execute(uint256,address,uint256,bytes) payable returns (bytes)'], wallet); await (await up.execute(0, recipient, ethers.parseEther('0.01'), '0x')).wait();
LSP25 Relay Signature — EIP-191 v0, do NOT use
:signMessage()
const encoded = ethers.solidityPacked( ['uint256','uint256','uint256','uint256','uint256','bytes'], [25, chainId, nonce, validityTimestamps, msgValue, payload] ); const prefix = new Uint8Array([0x19, 0x00]); const msg = new Uint8Array([...prefix, ...ethers.getBytes(kmAddress), ...ethers.getBytes(encoded)]); const signature = ethers.Signature.from(new ethers.SigningKey(privateKey).sign(ethers.keccak256(msg))).serialized;
Relay API:
POST https://relayer.mainnet.lukso.network/api/execute { "address": "0xUP", "transaction": { "abi": "0xpayload", "signature": "0x...", "nonce": 0, "validityTimestamps": "0x0" } }
The
payload for relay calls is the full UP.execute(...) calldata. The relay service calls KeyManager.executeRelayCall() — you never call the KM directly.
For
setData via relay, the payload is the setData(...) calldata (NOT wrapped in execute()).
Nonce channels:
getNonce(controller, channelId) — same channel = sequential, different = parallel.
Validity timestamps: (startTimestamp << 128) | endTimestamp. Use 0 for no restriction.
UPs can be redeployed at the same address on other chains by replaying the original LSP23 factory calldata.
| Contract | Address |
|---|---|
| LSP23 Factory | |
| UniversalProfileInit v0.14.0 | |
| LSP6KeyManagerInit v0.14.0 | |
| PostDeploymentModule | |
node commands/cross-chain-deploy-data.js <upAddress> [--verify]wallet.sendTransaction({ to: factoryAddress, data: calldata, value: 0n })| LSP | Interface ID | Name | Purpose |
|---|---|---|---|
| LSP0 | 0x24871b3d | ERC725Account | Smart contract account (UP) |
| LSP1 | 0x6bb56a14 | UniversalReceiver | Notification hooks |
| LSP2 | — | ERC725Y JSON Schema | Key encoding |
| LSP3 | — | Profile Metadata | Name, avatar, links, tags |
| LSP4 | — | Digital Asset Metadata | Token name, symbol, type |
| LSP5 | — | ReceivedAssets | Tracks owned tokens/NFTs |
| LSP6 | 0x23f34c62 | KeyManager | Permission-based access control |
| LSP7 | 0xc52d6008 | DigitalAsset | Fungible tokens (like ERC20) |
| LSP8 | 0x3a271706 | IdentifiableDigitalAsset | NFTs (bytes32 token IDs) |
| LSP9 | 0x28af17e6 | Vault | Sub-account for asset segregation |
| LSP14 | 0x94be5999 | Ownable2Step | Two-step ownership transfer |
| LSP25 | 0x5ac79908 | ExecuteRelayCall | Gasless meta-transactions (LUKSO only) |
| LSP26 | 0x2b299cea | FollowerSystem | On-chain follow/unfollow |
| LSP28 | — | TheGrid | Customizable profile grid layouts |
Full ABIs, interface IDs, and ERC725Y data keys in
lib/constants.js.
Contract:
0xf01103E5a9909Fc0DBe8166dA7085e0285daDDcA (LUKSO mainnet).
MUST route through UP via
execute() — never call directly from controller.
const followData = lsp26Iface.encodeFunctionData('follow', [targetAddress]); // Direct: km.execute(up.encodeFunctionData('execute', [0, LSP26_ADDR, 0, followData])) // Relay: sign + submit via relay API
Format:
0x + 00006f357c6a0020 (8-byte header) + keccak256hash (32 bytes) + url as UTF-8 hex
Header = verificationMethod(2) + hashFunction(4=keccak256(utf8)) + hashLength(2=0x0020).
Decoding: skip 80 hex chars (2 + 8 + 4 + 64 + 2 prefix), rest = UTF-8 URL.
Common mistakes: forgetting
0020 hash length bytes, not pinning IPFS before on-chain tx, hash mismatch from re-serialization.
getData(0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5) → decode VerifiableURI → fetch JSON{ verification: { method: "keccak256(bytes)", data: "0x..." }, url: "ipfs://..." } for imageskeccak256(exactJsonBytes), encode VerifiableURIsetData(LSP3_KEY, verifiableUri) from controllerLSP3 key:
0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5
LSP28 key: 0x724141d9918ce69e6b8afcf53a91748466086ba2c74b94cab43c649ae2ac23ff
Grid layout JSON at LSP28 key as VerifiableURI.
{ "LSP28TheGrid": [{ "title": "My Grid", "gridColumns": 2, "visibility": "public", "grid": [ { "width": 1, "height": 1, "type": "TEXT", "properties": { "title": "Hi", "text": "...", "backgroundColor": "#1a1a2e", "textColor": "#fff" } }, { "width": 2, "height": 2, "type": "IMAGES", "properties": { "type": "grid", "images": ["https://..."] } }, { "width": 1, "height": 1, "type": "X", "properties": { "type": "post", "username": "handle", "id": "tweetId", "theme": "dark" } } ] }] }
Types:
IFRAME, TEXT, IMAGES, X, INSTAGRAM, QR_CODE, ELFSIGHT. gridColumns 2–4, width/height 1–3.
Social NFT platform. Agent API at
https://www.forevermoments.life/api/agent/v1.
POST /moments/build-mint (or /collections/build-join, etc.) → get derived.upExecutePayloadPOST /relay/prepare with { upAddress, controllerAddress, payload } → get hashToSign, noncehashToSign as RAW DIGEST (SigningKey.sign(), NOT signMessage()) → POST /relay/submit/collections/build-join — join collection/collections/build-create + /collections/finalize-create — create collection (2-step)/moments/build-mint — mint Moment NFT/relay/prepare + /relay/submit — relay flow/api/pinata (NOT /api/agent/v1/pinata) — pin file to IPFS (multipart){ "LSP4Metadata": { "name": "Title", "description": "...", "images": [[{ "width": 1024, "height": 1024, "url": "ipfs://Qm..." }]], "icon": [{ "width": 1024, "height": 1024, "url": "ipfs://Qm..." }], "tags": ["tag1"], "createdAt": "2026-02-08T16:30:00.000Z" } }
Known collection "Art by the Machine":
0x439f6793b10b0a9d88ad05293a074a8141f19d77
https://www.forevermoments.life/collections/<addr>https://www.forevermoments.life/moments/<addr>https://www.forevermoments.life/profile/<addr>| Code | Cause |
|---|---|
| UP_PERMISSION_DENIED | Controller lacks required permission |
| UP_RELAY_FAILED | Relay error — check quota (LUKSO only) |
| UP_INVALID_SIGNATURE | Wrong chainId, used nonce, or expired timestamps |
| UP_QUOTA_EXCEEDED | Monthly relay quota exhausted (LUKSO only) |
| UP_NOT_AUTHORIZED | Not a controller — use Authorization UI |
config set restricted to safe keys only.Package:
@erc725/erc725.js — the standard library for reading, encoding, and decoding ERC725Y data on Universal Profiles. Use this for all profile data operations (reading profiles, permissions, assets, encoding setData payloads).
Full reference: references/ERC725-JS.md
import ERC725 from '@erc725/erc725.js'; import { LSP3Schema, LSP5Schema, LSP6Schema } from '@erc725/erc725.js/schemas';// Connect to a UP const erc725 = new ERC725(LSP3Schema, upAddress, 'https://42.rpc.thirdweb.com', { ipfsGateway: 'https://api.universalprofile.cloud/ipfs/', });
// Read profile (fetches + verifies IPFS content) const profile = await erc725.fetchData('LSP3Profile');
// Encode data for setData/setDataBatch const { keys, values } = erc725.encodeData([{ keyName: 'LSP3Profile', value: { json: profileJson, url: 'ipfs://Qm...' }, }]);
getData(key) — read raw decoded value from contractfetchData(key) — read + download linked content (IPFS) + verify hashencodeData(data) — encode for setData()/setDataBatch() → { keys, values }decodeData(data) — decode raw contract bytes back to structured dataencodeKeyName(name) — hash a key name to bytes32@erc725/erc725.js/schemas)// Read permissions const erc725 = new ERC725(LSP6Schema, upAddress, RPC_URL); const perms = await erc725.getData({ keyName: 'AddressPermissions:Permissions:<address>', dynamicKeyParts: [controllerAddress], });// Read received assets const erc725 = new ERC725(LSP5Schema, upAddress, RPC_URL); const assets = await erc725.getData('LSP5ReceivedAssets[]');
// Encode VerifiableURI (auto-hashes JSON) erc725.encodeData([{ keyName: 'LSP3Profile', value: { json: myJson, url: 'ipfs://QmNew...' }, }]);
totalArrayLength + startingIndex when modifying arrays to avoid corrupting contract statedynamicKeyParts for Mapping key types (e.g., permissions per address)isomorphic-fetch{ json, url } for auto-hashing, or { verification, url } for pre-computedNode.js 18+, ethers.js v6, @erc725/erc725.js, viem.
LUKSO Docs · Universal Everything · LSP6 Spec · Authorization UI
Profile URLs: always use
https://universaleverything.io/<address>No automatic installation available. Please visit the source repository for installation instructions.
View Installation Instructions1,500+ AI skills, agents & workflows. Install in 30 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.