Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
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.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
Authorize your bot: create a profile at my.universalprofile.cloud, generate a controller key, authorize via Authorization UI.
UP (Universal Profile) = smart contract account (LSP0/ERC725Account). This is the on-chain identity. KeyManager (LSP6) = access control. Controllers have permission bitmasks. Controller = EOA with permissions to act on behalf of the UP. All calls to external contracts MUST route through UP via execute() so msg.sender = UP address. Exception: 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.
ChainIDRPCExplorerRelayTokenLUKSO42https://42.rpc.thirdweb.comhttps://explorer.lukso.networkhttps://relayer.mainnet.lukso.network/apiLYXLUKSO Testnet4201https://rpc.testnet.lukso.networkhttps://explorer.testnet.lukso.networkhttps://relayer.testnet.lukso.network/apiLYXtBase8453https://mainnet.base.orghttps://basescan.org❌ETHEthereum1https://eth.llamarpc.comhttps://etherscan.io❌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.
PermissionHexRiskNotesCHANGEOWNER0x01🔴ADDCONTROLLER0x02🟠EDITPERMISSIONS0x04🟠ADDEXTENSIONS0x08🟡CHANGEEXTENSIONS0x10🟡ADDUNIVERSALRECEIVERDELEGATE0x20🟡CHANGEUNIVERSALRECEIVERDELEGATE0x40🟡REENTRANCY0x80🟡SUPER_TRANSFERVALUE0x0100🟠Any recipientTRANSFERVALUE0x0200🟡AllowedCalls onlySUPER_CALL0x0400🟠Any contractCALL0x0800🟡AllowedCalls onlySUPER_STATICCALL0x1000🟢STATICCALL0x2000🟢SUPER_DELEGATECALL0x4000🔴DELEGATECALL0x8000🔴DEPLOY0x010000🟡SUPER_SETDATA0x020000🟠Any keySETDATA0x040000🟡AllowedERC725YDataKeys onlyENCRYPT0x080000🟢DECRYPT0x100000🟢SIGN0x200000🟢EXECUTE_RELAY_CALL0x400000🟢 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.
ContractAddressLSP23 Factory0x2300000A84D25dF63081feAa37ba6b62C4c89a30UniversalProfileInit v0.14.00x3024D38EA2434BA6635003Dc1BDC0daB5882ED4FLSP6KeyManagerInit v0.14.00x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4PostDeploymentModule0x000000000066093407b6704B89793beFfD0D8F00
Retrieve original deployment calldata: node commands/cross-chain-deploy-data.js <upAddress> [--verify] Fund controller with ETH on target chain Submit same calldata to factory: wallet.sendTransaction({ to: factoryAddress, data: calldata, value: 0n }) Authorize controller on new chain via Authorization UI (permissions are per-chain)
Legacy UPs (pre-LSP23, old lsp-factory) have no deployment events Determinism requires identical salt + implementations + init data
LSPInterface IDNamePurposeLSP00x24871b3dERC725AccountSmart contract account (UP)LSP10x6bb56a14UniversalReceiverNotification hooksLSP2—ERC725Y JSON SchemaKey encodingLSP3—Profile MetadataName, avatar, links, tagsLSP4—Digital Asset MetadataToken name, symbol, typeLSP5—ReceivedAssetsTracks owned tokens/NFTsLSP60x23f34c62KeyManagerPermission-based access controlLSP70xc52d6008DigitalAssetFungible tokens (like ERC20)LSP80x3a271706IdentifiableDigitalAssetNFTs (bytes32 token IDs)LSP90x28af17e6VaultSub-account for asset segregationLSP140x94be5999Ownable2StepTwo-step ownership transferLSP250x5ac79908ExecuteRelayCallGasless meta-transactions (LUKSO only)LSP260x2b299ceaFollowerSystemOn-chain follow/unfollowLSP28—TheGridCustomizable 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.
Read current: getData(0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5) → decode VerifiableURI → fetch JSON Modify JSON Use { verification: { method: "keccak256(bytes)", data: "0x..." }, url: "ipfs://..." } for images Pin images + JSON to IPFS, verify accessible via gateway Compute keccak256(exactJsonBytes), encode VerifiableURI setData(LSP3_KEY, verifiableUri) from controller Verify: read back, decode, fetch, confirm LSP3 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.
Build — POST /moments/build-mint (or /collections/build-join, etc.) → get derived.upExecutePayload Prepare — POST /relay/prepare with { upAddress, controllerAddress, payload } → get hashToSign, nonce Sign & Submit — sign hashToSign 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
Collection: https://www.forevermoments.life/collections/<addr> Moment: https://www.forevermoments.life/moments/<addr> Profile: https://www.forevermoments.life/profile/<addr>
CodeCauseUP_PERMISSION_DENIEDController lacks required permissionUP_RELAY_FAILEDRelay error — check quota (LUKSO only)UP_INVALID_SIGNATUREWrong chainId, used nonce, or expired timestampsUP_QUOTA_EXCEEDEDMonthly relay quota exhausted (LUKSO only)UP_NOT_AUTHORIZEDNot a controller — use Authorization UI
Grant minimum permissions. Prefer CALL over SUPER_CALL. Use AllowedCalls/AllowedERC725YDataKeys to restrict. Avoid DELEGATECALL and CHANGEOWNER unless necessary. Never log/print/transmit private keys. Test on testnet (4201) first. config set restricted to safe keys only.
Node.js 18+, ethers.js v6, viem.
LUKSO Docs · Universal Everything · LSP6 Spec · Authorization UI Profile URLs: always use https://universaleverything.io/<address>
Agent frameworks, memory systems, reasoning layers, and model-native orchestration.
Largest current source with strong distribution and engagement signals.