{
  "schemaVersion": "1.0",
  "item": {
    "slug": "universal-profile",
    "name": "Universal Profile",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/frozeman/universal-profile",
    "canonicalUrl": "https://clawhub.ai/frozeman/universal-profile",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/universal-profile",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=universal-profile",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "index.js",
      "package-lock.json",
      "package.json",
      "SKILL.md",
      "commands/transfer-lsp7.js",
      "commands/query-assets.js"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "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."
        },
        {
          "label": "Upgrade existing",
          "body": "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."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/universal-profile"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/universal-profile",
    "agentPageUrl": "https://openagent3.xyz/skills/universal-profile/agent",
    "manifestUrl": "https://openagent3.xyz/skills/universal-profile/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/universal-profile/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "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."
      },
      {
        "label": "Upgrade existing",
        "body": "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."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Universal Profile Skill",
        "body": "Authorize your bot: create a profile at my.universalprofile.cloud, generate a controller key, authorize via Authorization UI."
      },
      {
        "title": "Core Concepts",
        "body": "UP (Universal Profile) = smart contract account (LSP0/ERC725Account). This is the on-chain identity.\nKeyManager (LSP6) = access control. Controllers have permission bitmasks.\nController = EOA with permissions to act on behalf of the UP.\nAll calls to external contracts MUST route through UP via execute() so msg.sender = UP address.\nException: setData()/setDataBatch() can be called directly on UP (checks permissions internally)."
      },
      {
        "title": "Direct (all chains — controller pays gas)",
        "body": "Controller → UP.execute(operation, target, value, data) → Target\n\nThe 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."
      },
      {
        "title": "Gasless Relay (LUKSO ONLY — chains 42/4201)",
        "body": "Controller signs LSP25 → Relay API submits → KeyManager.executeRelayCall() → UP\n\nThe controller signs a message, then the LUKSO relay service submits the transaction. Do NOT call executeRelayCall() yourself — the relay API does this.\n\n⚠️ 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.\n\nTypical gas costs: LUKSO ~free via relay, Base ~$0.001-0.01/tx, Ethereum ~$0.10-1.00/tx."
      },
      {
        "title": "Networks",
        "body": "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"
      },
      {
        "title": "CLI",
        "body": "up status                                      # Config, keys, connectivity\nup profile info [<address>] [--chain <chain>]  # Profile details\nup profile configure <address> [--chain lukso]  # Save UP for use\nup key generate [--save] [--password <pw>]     # Generate controller keypair\nup permissions encode <perm1> [<perm2> ...]    # Encode to bytes32\nup permissions decode <hex>                    # Decode to names\nup permissions presets                         # List presets\nup authorize url [--permissions <preset|hex>]  # Generate auth URL\nup quota                                       # Check relay gas quota (LUKSO only)\n\nPresets: read-only 🟢 | token-operator 🟡 | nft-trader 🟡 | defi-trader 🟠 | profile-manager 🟡 | full-access 🔴"
      },
      {
        "title": "Credentials",
        "body": "Config lookup order: UP_CREDENTIALS_PATH env → ~/.openclaw/universal-profile/config.json → ~/.clawdbot/universal-profile/config.json\n\nKey lookup order: UP_KEY_PATH env → ~/.openclaw/credentials/universal-profile-key.json → ~/.clawdbot/credentials/universal-profile-key.json\n\nCanonical path for new credentials: ~/.openclaw/credentials/universal-profile-key.json\n\nSkill config path: ~/.openclaw/skills/universal-profile/config.json\n\nExpected JSON format:\n\n{\n  \"universalProfile\": {\n    \"address\": \"0xYourUniversalProfileAddress\"\n  },\n  \"controller\": {\n    \"address\": \"0xYourControllerAddress\",\n    \"privateKey\": \"0xYourPrivateKey\"\n  }\n}\n\nKey file permissions: chmod 600. Keys loaded only for signing, then cleared. The skill warns if credential files are readable by group/others."
      },
      {
        "title": "Permissions (bytes32 BitArray)",
        "body": "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🟢\n\nSUPER variants = unrestricted. Regular = restricted to AllowedCalls/AllowedERC725YDataKeys. Prefer restricted."
      },
      {
        "title": "Direct Execution (all chains)",
        "body": "// Controller calls UP.execute() directly — works on LUKSO, Base, Ethereum\nconst provider = new ethers.JsonRpcProvider(rpcUrl);  // use correct RPC for chain\nconst wallet = new ethers.Wallet(controllerPrivateKey, provider);\nconst up = new ethers.Contract(upAddress, ['function execute(uint256,address,uint256,bytes) payable returns (bytes)'], wallet);\nawait (await up.execute(0, recipient, ethers.parseEther('0.01'), '0x')).wait();"
      },
      {
        "title": "Gasless Relay (LUKSO only)",
        "body": "LSP25 Relay Signature — EIP-191 v0, do NOT use signMessage():\n\nconst encoded = ethers.solidityPacked(\n  ['uint256','uint256','uint256','uint256','uint256','bytes'],\n  [25, chainId, nonce, validityTimestamps, msgValue, payload]\n);\nconst prefix = new Uint8Array([0x19, 0x00]);\nconst msg = new Uint8Array([...prefix, ...ethers.getBytes(kmAddress), ...ethers.getBytes(encoded)]);\nconst signature = ethers.Signature.from(new ethers.SigningKey(privateKey).sign(ethers.keccak256(msg))).serialized;\n\nRelay API:\n\nPOST https://relayer.mainnet.lukso.network/api/execute\n{ \"address\": \"0xUP\", \"transaction\": { \"abi\": \"0xpayload\", \"signature\": \"0x...\", \"nonce\": 0, \"validityTimestamps\": \"0x0\" } }\n\nThe payload for relay calls is the full UP.execute(...) calldata. The relay service calls KeyManager.executeRelayCall() — you never call the KM directly.\n\nFor setData via relay, the payload is the setData(...) calldata (NOT wrapped in execute()).\n\nNonce channels: getNonce(controller, channelId) — same channel = sequential, different = parallel.\nValidity timestamps: (startTimestamp << 128) | endTimestamp. Use 0 for no restriction."
      },
      {
        "title": "Cross-Chain Deployment (LSP23)",
        "body": "UPs can be redeployed at the same address on other chains by replaying the original LSP23 factory calldata."
      },
      {
        "title": "Factory & Implementations (identical addresses on LUKSO, Base, Ethereum)",
        "body": "ContractAddressLSP23 Factory0x2300000A84D25dF63081feAa37ba6b62C4c89a30UniversalProfileInit v0.14.00x3024D38EA2434BA6635003Dc1BDC0daB5882ED4FLSP6KeyManagerInit v0.14.00x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4PostDeploymentModule0x000000000066093407b6704B89793beFfD0D8F00"
      },
      {
        "title": "Workflow",
        "body": "Retrieve original deployment calldata: node commands/cross-chain-deploy-data.js <upAddress> [--verify]\nFund controller with ETH on target chain\nSubmit same calldata to factory: wallet.sendTransaction({ to: factoryAddress, data: calldata, value: 0n })\nAuthorize controller on new chain via Authorization UI (permissions are per-chain)"
      },
      {
        "title": "Limitations",
        "body": "Legacy UPs (pre-LSP23, old lsp-factory) have no deployment events\nDeterminism requires identical salt + implementations + init data"
      },
      {
        "title": "LSP Ecosystem",
        "body": "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\n\nFull ABIs, interface IDs, and ERC725Y data keys in lib/constants.js."
      },
      {
        "title": "LSP26 Follow/Unfollow",
        "body": "Contract: 0xf01103E5a9909Fc0DBe8166dA7085e0285daDDcA (LUKSO mainnet).\n\nMUST route through UP via execute() — never call directly from controller.\n\nconst followData = lsp26Iface.encodeFunctionData('follow', [targetAddress]);\n// Direct: km.execute(up.encodeFunctionData('execute', [0, LSP26_ADDR, 0, followData]))\n// Relay: sign + submit via relay API"
      },
      {
        "title": "VerifiableURI (LSP2)",
        "body": "Format: 0x + 00006f357c6a0020 (8-byte header) + keccak256hash (32 bytes) + url as UTF-8 hex\n\nHeader = verificationMethod(2) + hashFunction(4=keccak256(utf8)) + hashLength(2=0x0020).\n\nDecoding: skip 80 hex chars (2 + 8 + 4 + 64 + 2 prefix), rest = UTF-8 URL.\n\nCommon mistakes: forgetting 0020 hash length bytes, not pinning IPFS before on-chain tx, hash mismatch from re-serialization."
      },
      {
        "title": "LSP3 Profile Update Procedure",
        "body": "Read current: getData(0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5) → decode VerifiableURI → fetch JSON\nModify JSON\nUse { verification: { method: \"keccak256(bytes)\", data: \"0x...\" }, url: \"ipfs://...\" } for images\nPin images + JSON to IPFS, verify accessible via gateway\nCompute keccak256(exactJsonBytes), encode VerifiableURI\nsetData(LSP3_KEY, verifiableUri) from controller\nVerify: read back, decode, fetch, confirm\n\nLSP3 key: 0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5\nLSP28 key: 0x724141d9918ce69e6b8afcf53a91748466086ba2c74b94cab43c649ae2ac23ff"
      },
      {
        "title": "LSP28 TheGrid",
        "body": "Grid layout JSON at LSP28 key as VerifiableURI.\n\n{ \"LSP28TheGrid\": [{ \"title\": \"My Grid\", \"gridColumns\": 2, \"visibility\": \"public\",\n  \"grid\": [\n    { \"width\": 1, \"height\": 1, \"type\": \"TEXT\", \"properties\": { \"title\": \"Hi\", \"text\": \"...\", \"backgroundColor\": \"#1a1a2e\", \"textColor\": \"#fff\" } },\n    { \"width\": 2, \"height\": 2, \"type\": \"IMAGES\", \"properties\": { \"type\": \"grid\", \"images\": [\"https://...\"] } },\n    { \"width\": 1, \"height\": 1, \"type\": \"X\", \"properties\": { \"type\": \"post\", \"username\": \"handle\", \"id\": \"tweetId\", \"theme\": \"dark\" } }\n  ]\n}] }\n\nTypes: IFRAME, TEXT, IMAGES, X, INSTAGRAM, QR_CODE, ELFSIGHT. gridColumns 2–4, width/height 1–3."
      },
      {
        "title": "Forever Moments (LUKSO only)",
        "body": "Social NFT platform. Agent API at https://www.forevermoments.life/api/agent/v1."
      },
      {
        "title": "3-Step Relay Flow",
        "body": "Build — POST /moments/build-mint (or /collections/build-join, etc.) → get derived.upExecutePayload\nPrepare — POST /relay/prepare with { upAddress, controllerAddress, payload } → get hashToSign, nonce\nSign & Submit — sign hashToSign as RAW DIGEST (SigningKey.sign(), NOT signMessage()) → POST /relay/submit"
      },
      {
        "title": "Endpoints",
        "body": "/collections/build-join — join collection\n/collections/build-create + /collections/finalize-create — create collection (2-step)\n/moments/build-mint — mint Moment NFT\n/relay/prepare + /relay/submit — relay flow\n/api/pinata (NOT /api/agent/v1/pinata) — pin file to IPFS (multipart)"
      },
      {
        "title": "Metadata (LSP4)",
        "body": "{ \"LSP4Metadata\": { \"name\": \"Title\", \"description\": \"...\",\n  \"images\": [[{ \"width\": 1024, \"height\": 1024, \"url\": \"ipfs://Qm...\" }]],\n  \"icon\": [{ \"width\": 1024, \"height\": 1024, \"url\": \"ipfs://Qm...\" }],\n  \"tags\": [\"tag1\"], \"createdAt\": \"2026-02-08T16:30:00.000Z\" } }\n\nKnown collection \"Art by the Machine\": 0x439f6793b10b0a9d88ad05293a074a8141f19d77"
      },
      {
        "title": "URLs",
        "body": "Collection: https://www.forevermoments.life/collections/<addr>\nMoment: https://www.forevermoments.life/moments/<addr>\nProfile: https://www.forevermoments.life/profile/<addr>"
      },
      {
        "title": "Error Codes",
        "body": "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"
      },
      {
        "title": "Security",
        "body": "Grant minimum permissions. Prefer CALL over SUPER_CALL.\nUse AllowedCalls/AllowedERC725YDataKeys to restrict.\nAvoid DELEGATECALL and CHANGEOWNER unless necessary.\nNever log/print/transmit private keys.\nTest on testnet (4201) first.\nconfig set restricted to safe keys only."
      },
      {
        "title": "Dependencies",
        "body": "Node.js 18+, ethers.js v6, viem."
      },
      {
        "title": "Links",
        "body": "LUKSO Docs · Universal Everything · LSP6 Spec · Authorization UI\n\nProfile URLs: always use https://universaleverything.io/<address>"
      }
    ],
    "body": "Universal Profile Skill\n\nAuthorize your bot: create a profile at my.universalprofile.cloud, generate a controller key, authorize via Authorization UI.\n\nCore Concepts\nUP (Universal Profile) = smart contract account (LSP0/ERC725Account). This is the on-chain identity.\nKeyManager (LSP6) = access control. Controllers have permission bitmasks.\nController = EOA with permissions to act on behalf of the UP.\nAll calls to external contracts MUST route through UP via execute() so msg.sender = UP address.\nException: setData()/setDataBatch() can be called directly on UP (checks permissions internally).\nExecution Models\nDirect (all chains — controller pays gas)\nController → UP.execute(operation, target, value, data) → Target\n\n\nThe 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.\n\nGasless Relay (LUKSO ONLY — chains 42/4201)\nController signs LSP25 → Relay API submits → KeyManager.executeRelayCall() → UP\n\n\nThe controller signs a message, then the LUKSO relay service submits the transaction. Do NOT call executeRelayCall() yourself — the relay API does this.\n\n⚠️ 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.\n\nTypical gas costs: LUKSO ~free via relay, Base ~$0.001-0.01/tx, Ethereum ~$0.10-1.00/tx.\n\nNetworks\nChain\tID\tRPC\tExplorer\tRelay\tToken\nLUKSO\t42\thttps://42.rpc.thirdweb.com\thttps://explorer.lukso.network\thttps://relayer.mainnet.lukso.network/api\tLYX\nLUKSO Testnet\t4201\thttps://rpc.testnet.lukso.network\thttps://explorer.testnet.lukso.network\thttps://relayer.testnet.lukso.network/api\tLYXt\nBase\t8453\thttps://mainnet.base.org\thttps://basescan.org\t❌\tETH\nEthereum\t1\thttps://eth.llamarpc.com\thttps://etherscan.io\t❌\tETH\nCLI\nup status                                      # Config, keys, connectivity\nup profile info [<address>] [--chain <chain>]  # Profile details\nup profile configure <address> [--chain lukso]  # Save UP for use\nup key generate [--save] [--password <pw>]     # Generate controller keypair\nup permissions encode <perm1> [<perm2> ...]    # Encode to bytes32\nup permissions decode <hex>                    # Decode to names\nup permissions presets                         # List presets\nup authorize url [--permissions <preset|hex>]  # Generate auth URL\nup quota                                       # Check relay gas quota (LUKSO only)\n\n\nPresets: read-only 🟢 | token-operator 🟡 | nft-trader 🟡 | defi-trader 🟠 | profile-manager 🟡 | full-access 🔴\n\nCredentials\n\nConfig lookup order: UP_CREDENTIALS_PATH env → ~/.openclaw/universal-profile/config.json → ~/.clawdbot/universal-profile/config.json\n\nKey lookup order: UP_KEY_PATH env → ~/.openclaw/credentials/universal-profile-key.json → ~/.clawdbot/credentials/universal-profile-key.json\n\nCanonical path for new credentials: ~/.openclaw/credentials/universal-profile-key.json\n\nSkill config path: ~/.openclaw/skills/universal-profile/config.json\n\nExpected JSON format:\n\n{\n  \"universalProfile\": {\n    \"address\": \"0xYourUniversalProfileAddress\"\n  },\n  \"controller\": {\n    \"address\": \"0xYourControllerAddress\",\n    \"privateKey\": \"0xYourPrivateKey\"\n  }\n}\n\n\nKey file permissions: chmod 600. Keys loaded only for signing, then cleared. The skill warns if credential files are readable by group/others.\n\nPermissions (bytes32 BitArray)\nPermission\tHex\tRisk\tNotes\nCHANGEOWNER\t0x01\t🔴\t\nADDCONTROLLER\t0x02\t🟠\t\nEDITPERMISSIONS\t0x04\t🟠\t\nADDEXTENSIONS\t0x08\t🟡\t\nCHANGEEXTENSIONS\t0x10\t🟡\t\nADDUNIVERSALRECEIVERDELEGATE\t0x20\t🟡\t\nCHANGEUNIVERSALRECEIVERDELEGATE\t0x40\t🟡\t\nREENTRANCY\t0x80\t🟡\t\nSUPER_TRANSFERVALUE\t0x0100\t🟠\tAny recipient\nTRANSFERVALUE\t0x0200\t🟡\tAllowedCalls only\nSUPER_CALL\t0x0400\t🟠\tAny contract\nCALL\t0x0800\t🟡\tAllowedCalls only\nSUPER_STATICCALL\t0x1000\t🟢\t\nSTATICCALL\t0x2000\t🟢\t\nSUPER_DELEGATECALL\t0x4000\t🔴\t\nDELEGATECALL\t0x8000\t🔴\t\nDEPLOY\t0x010000\t🟡\t\nSUPER_SETDATA\t0x020000\t🟠\tAny key\nSETDATA\t0x040000\t🟡\tAllowedERC725YDataKeys only\nENCRYPT\t0x080000\t🟢\t\nDECRYPT\t0x100000\t🟢\t\nSIGN\t0x200000\t🟢\t\nEXECUTE_RELAY_CALL\t0x400000\t🟢\t\n\nSUPER variants = unrestricted. Regular = restricted to AllowedCalls/AllowedERC725YDataKeys. Prefer restricted.\n\nTransactions\nDirect Execution (all chains)\n// Controller calls UP.execute() directly — works on LUKSO, Base, Ethereum\nconst provider = new ethers.JsonRpcProvider(rpcUrl);  // use correct RPC for chain\nconst wallet = new ethers.Wallet(controllerPrivateKey, provider);\nconst up = new ethers.Contract(upAddress, ['function execute(uint256,address,uint256,bytes) payable returns (bytes)'], wallet);\nawait (await up.execute(0, recipient, ethers.parseEther('0.01'), '0x')).wait();\n\nGasless Relay (LUKSO only)\n\nLSP25 Relay Signature — EIP-191 v0, do NOT use signMessage():\n\nconst encoded = ethers.solidityPacked(\n  ['uint256','uint256','uint256','uint256','uint256','bytes'],\n  [25, chainId, nonce, validityTimestamps, msgValue, payload]\n);\nconst prefix = new Uint8Array([0x19, 0x00]);\nconst msg = new Uint8Array([...prefix, ...ethers.getBytes(kmAddress), ...ethers.getBytes(encoded)]);\nconst signature = ethers.Signature.from(new ethers.SigningKey(privateKey).sign(ethers.keccak256(msg))).serialized;\n\n\nRelay API:\n\nPOST https://relayer.mainnet.lukso.network/api/execute\n{ \"address\": \"0xUP\", \"transaction\": { \"abi\": \"0xpayload\", \"signature\": \"0x...\", \"nonce\": 0, \"validityTimestamps\": \"0x0\" } }\n\n\nThe payload for relay calls is the full UP.execute(...) calldata. The relay service calls KeyManager.executeRelayCall() — you never call the KM directly.\n\nFor setData via relay, the payload is the setData(...) calldata (NOT wrapped in execute()).\n\nNonce channels: getNonce(controller, channelId) — same channel = sequential, different = parallel. Validity timestamps: (startTimestamp << 128) | endTimestamp. Use 0 for no restriction.\n\nCross-Chain Deployment (LSP23)\n\nUPs can be redeployed at the same address on other chains by replaying the original LSP23 factory calldata.\n\nFactory & Implementations (identical addresses on LUKSO, Base, Ethereum)\nContract\tAddress\nLSP23 Factory\t0x2300000A84D25dF63081feAa37ba6b62C4c89a30\nUniversalProfileInit v0.14.0\t0x3024D38EA2434BA6635003Dc1BDC0daB5882ED4F\nLSP6KeyManagerInit v0.14.0\t0x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4\nPostDeploymentModule\t0x000000000066093407b6704B89793beFfD0D8F00\nWorkflow\nRetrieve original deployment calldata: node commands/cross-chain-deploy-data.js <upAddress> [--verify]\nFund controller with ETH on target chain\nSubmit same calldata to factory: wallet.sendTransaction({ to: factoryAddress, data: calldata, value: 0n })\nAuthorize controller on new chain via Authorization UI (permissions are per-chain)\nLimitations\nLegacy UPs (pre-LSP23, old lsp-factory) have no deployment events\nDeterminism requires identical salt + implementations + init data\nLSP Ecosystem\nLSP\tInterface ID\tName\tPurpose\nLSP0\t0x24871b3d\tERC725Account\tSmart contract account (UP)\nLSP1\t0x6bb56a14\tUniversalReceiver\tNotification hooks\nLSP2\t—\tERC725Y JSON Schema\tKey encoding\nLSP3\t—\tProfile Metadata\tName, avatar, links, tags\nLSP4\t—\tDigital Asset Metadata\tToken name, symbol, type\nLSP5\t—\tReceivedAssets\tTracks owned tokens/NFTs\nLSP6\t0x23f34c62\tKeyManager\tPermission-based access control\nLSP7\t0xc52d6008\tDigitalAsset\tFungible tokens (like ERC20)\nLSP8\t0x3a271706\tIdentifiableDigitalAsset\tNFTs (bytes32 token IDs)\nLSP9\t0x28af17e6\tVault\tSub-account for asset segregation\nLSP14\t0x94be5999\tOwnable2Step\tTwo-step ownership transfer\nLSP25\t0x5ac79908\tExecuteRelayCall\tGasless meta-transactions (LUKSO only)\nLSP26\t0x2b299cea\tFollowerSystem\tOn-chain follow/unfollow\nLSP28\t—\tTheGrid\tCustomizable profile grid layouts\n\nFull ABIs, interface IDs, and ERC725Y data keys in lib/constants.js.\n\nLSP26 Follow/Unfollow\n\nContract: 0xf01103E5a9909Fc0DBe8166dA7085e0285daDDcA (LUKSO mainnet).\n\nMUST route through UP via execute() — never call directly from controller.\n\nconst followData = lsp26Iface.encodeFunctionData('follow', [targetAddress]);\n// Direct: km.execute(up.encodeFunctionData('execute', [0, LSP26_ADDR, 0, followData]))\n// Relay: sign + submit via relay API\n\nVerifiableURI (LSP2)\n\nFormat: 0x + 00006f357c6a0020 (8-byte header) + keccak256hash (32 bytes) + url as UTF-8 hex\n\nHeader = verificationMethod(2) + hashFunction(4=keccak256(utf8)) + hashLength(2=0x0020).\n\nDecoding: skip 80 hex chars (2 + 8 + 4 + 64 + 2 prefix), rest = UTF-8 URL.\n\nCommon mistakes: forgetting 0020 hash length bytes, not pinning IPFS before on-chain tx, hash mismatch from re-serialization.\n\nLSP3 Profile Update Procedure\nRead current: getData(0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5) → decode VerifiableURI → fetch JSON\nModify JSON\nUse { verification: { method: \"keccak256(bytes)\", data: \"0x...\" }, url: \"ipfs://...\" } for images\nPin images + JSON to IPFS, verify accessible via gateway\nCompute keccak256(exactJsonBytes), encode VerifiableURI\nsetData(LSP3_KEY, verifiableUri) from controller\nVerify: read back, decode, fetch, confirm\n\nLSP3 key: 0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5 LSP28 key: 0x724141d9918ce69e6b8afcf53a91748466086ba2c74b94cab43c649ae2ac23ff\n\nLSP28 TheGrid\n\nGrid layout JSON at LSP28 key as VerifiableURI.\n\n{ \"LSP28TheGrid\": [{ \"title\": \"My Grid\", \"gridColumns\": 2, \"visibility\": \"public\",\n  \"grid\": [\n    { \"width\": 1, \"height\": 1, \"type\": \"TEXT\", \"properties\": { \"title\": \"Hi\", \"text\": \"...\", \"backgroundColor\": \"#1a1a2e\", \"textColor\": \"#fff\" } },\n    { \"width\": 2, \"height\": 2, \"type\": \"IMAGES\", \"properties\": { \"type\": \"grid\", \"images\": [\"https://...\"] } },\n    { \"width\": 1, \"height\": 1, \"type\": \"X\", \"properties\": { \"type\": \"post\", \"username\": \"handle\", \"id\": \"tweetId\", \"theme\": \"dark\" } }\n  ]\n}] }\n\n\nTypes: IFRAME, TEXT, IMAGES, X, INSTAGRAM, QR_CODE, ELFSIGHT. gridColumns 2–4, width/height 1–3.\n\nForever Moments (LUKSO only)\n\nSocial NFT platform. Agent API at https://www.forevermoments.life/api/agent/v1.\n\n3-Step Relay Flow\nBuild — POST /moments/build-mint (or /collections/build-join, etc.) → get derived.upExecutePayload\nPrepare — POST /relay/prepare with { upAddress, controllerAddress, payload } → get hashToSign, nonce\nSign & Submit — sign hashToSign as RAW DIGEST (SigningKey.sign(), NOT signMessage()) → POST /relay/submit\nEndpoints\n/collections/build-join — join collection\n/collections/build-create + /collections/finalize-create — create collection (2-step)\n/moments/build-mint — mint Moment NFT\n/relay/prepare + /relay/submit — relay flow\n/api/pinata (NOT /api/agent/v1/pinata) — pin file to IPFS (multipart)\nMetadata (LSP4)\n{ \"LSP4Metadata\": { \"name\": \"Title\", \"description\": \"...\",\n  \"images\": [[{ \"width\": 1024, \"height\": 1024, \"url\": \"ipfs://Qm...\" }]],\n  \"icon\": [{ \"width\": 1024, \"height\": 1024, \"url\": \"ipfs://Qm...\" }],\n  \"tags\": [\"tag1\"], \"createdAt\": \"2026-02-08T16:30:00.000Z\" } }\n\n\nKnown collection \"Art by the Machine\": 0x439f6793b10b0a9d88ad05293a074a8141f19d77\n\nURLs\nCollection: https://www.forevermoments.life/collections/<addr>\nMoment: https://www.forevermoments.life/moments/<addr>\nProfile: https://www.forevermoments.life/profile/<addr>\nError Codes\nCode\tCause\nUP_PERMISSION_DENIED\tController lacks required permission\nUP_RELAY_FAILED\tRelay error — check quota (LUKSO only)\nUP_INVALID_SIGNATURE\tWrong chainId, used nonce, or expired timestamps\nUP_QUOTA_EXCEEDED\tMonthly relay quota exhausted (LUKSO only)\nUP_NOT_AUTHORIZED\tNot a controller — use Authorization UI\nSecurity\nGrant minimum permissions. Prefer CALL over SUPER_CALL.\nUse AllowedCalls/AllowedERC725YDataKeys to restrict.\nAvoid DELEGATECALL and CHANGEOWNER unless necessary.\nNever log/print/transmit private keys.\nTest on testnet (4201) first.\nconfig set restricted to safe keys only.\nDependencies\n\nNode.js 18+, ethers.js v6, viem.\n\nLinks\n\nLUKSO Docs · Universal Everything · LSP6 Spec · Authorization UI\n\nProfile URLs: always use https://universaleverything.io/<address>"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/frozeman/universal-profile",
    "publisherUrl": "https://clawhub.ai/frozeman/universal-profile",
    "owner": "frozeman",
    "version": "0.8.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/universal-profile",
    "downloadUrl": "https://openagent3.xyz/downloads/universal-profile",
    "agentUrl": "https://openagent3.xyz/skills/universal-profile/agent",
    "manifestUrl": "https://openagent3.xyz/skills/universal-profile/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/universal-profile/agent.md"
  }
}