Skip to content

Why LUKSO? - New Learn Section - #1384

Merged
JordyDutch merged 7 commits into
lukso-network:mainfrom
tehnalogos:learn-upgrade
Jul 23, 2026
Merged

Why LUKSO? - New Learn Section#1384
JordyDutch merged 7 commits into
lukso-network:mainfrom
tehnalogos:learn-upgrade

Conversation

@tehnalogos

@tehnalogos tehnalogos commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

telegram-cloud-photo-size-4-5983501936189181822-y

@JordyDutch
JordyDutch self-requested a review July 22, 2026 20:57
@JordyDutch

JordyDutch commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

LUKSO automated technical review — request changes

I reviewed the complete 55-file diff at c8a927b2f35da881ac88a477e7398b632ff8a377 against the current LUKSO standards, generated contract APIs, EIPs, and Safe documentation.

The section builds successfully, but it is not technically correct enough to approve yet. The following issues should be resolved before merge.

Merge blockers

  1. ERC-1155 balances can be irreversibly merged

    The migration guide maps every fungible ERC-1155 ID into one LSP7 contract. LSP7 has one balance per address and no token-ID dimension. For example, ERC-1155 ID 1 with 5 units and ID 2 with 7 units would become one indistinguishable LSP7 balance of 12. Each distinct fungible asset normally needs its own LSP7 contract, unless merging them is explicitly intended. The guide also presents ERC-1155's optional high-bit encoding example as though it were mandatory.

    Sources: ERC-1155, LSP7.

  2. The Safe asset-transfer instructions are not executable as written

    In lines 34–36:

    • profile.execute(...) spends the profile's funds; it does not move LYX out of the Safe. The Safe should send value directly to the profile.
    • token.transfer(profile, balance) is valid ERC-20 syntax but not LSP7. LSP7 requires transfer(from,to,amount,force,data).
    • ERC-721 safeTransferFrom to a default Universal Profile reverts unless the OnERC721ReceivedExtension has first been registered through LSP17.
    • The later "Old Safe paused" check is unsupported because Safe has no generic pause function.

    Sources: LSP0, LSP7, Safe execTransaction.

  3. LSP25 is assigned to the wrong contract and a required permission is omitted

    Several pages say executeRelayCall exists directly on the Universal Profile. In the canonical UP setup it is called on the LSP6 Key Manager, which verifies the signature and permissions before executing the payload on the profile. The signing controller also needs EXECUTE_RELAY_CALL, which the new guide does not mention. Using the documented UP endpoint or omitting that permission will revert.

    Sources: LSP25, LSP6 relay execution.

  4. Revoking an LSP6 controller does not revoke an existing LSP7 operator allowance

    The approval-risk page says controller revocation removes the operator's authority account-wide. The allowance remains stored in the token contract for (operator, tokenOwner). The operator can continue transferring or burning until the allowance is exhausted or explicitly removed with revokeOperator. Revoking the controller that originally created the allowance does not change that token-level authorization.

    Source: LSP7 operator semantics.

Additional confirmed technical corrections

  • EIP-7702: It is described as temporary/per-transaction and as requiring an ERC-4337 bundler/paymaster for sponsorship in this comparison. Delegation persists until replaced or cleared, and a separate transaction sender can pay gas without inherently using ERC-4337. EIP-7702.

  • Account model: Universal Profiles are repeatedly called the protocol/default account type. LUKSO runs an unmodified Ethereum protocol; UPs are the ecosystem's standardized smart-contract account, not a consensus-level replacement for EOAs. "Ecosystem/product default" would be accurate.

  • Token IDs: The ERC-721 token-ID comparison says a uint256 cannot hold a full hash, serial or structured reference. uint256 and bytes32 are both exactly 256 bits. LSP8's advantage is its declared interpretation formats, not additional capacity. LSP8 token-ID formats.

  • Metadata: Several pages claim LSP3/LSP4 JSON metadata itself is on-chain or chain-enforced, including the marketplace comparison. Normally the contract stores a VerifiableURI containing a URI and hash; the JSON can be on centralized or decentralized storage. Clients verify the hash—the chain does not retrieve/enforce the remote file. Fully inline data: storage is possible but optional. LSP4 metadata.

  • LSP17: The comparison incorrectly says LSP17 can use DELEGATECALL; the standard uses CALL. Extension mappings can also be changed or removed, so "purely additive" is only valid for immutable native selectors. LSP17.

  • LSP1 / force: "universalReceiver on every transfer" is too absolute. EOAs and non-LSP1 contracts receive no callback when force=true; force is a required explicit argument and has no default. The PR also names a separate IERC1155BatchReceiver, but ERC-1155 defines one receiver interface containing both single and batch callbacks.

  • LSP26: Followers are said to live directly on the UP. LSP26 is a separate canonical follower-registry contract. LSP26.

  • Unsupported capability claims: LSP6 has no controller-expiry/time-window field; LSP25 validity timestamps constrain individual relay signatures. LSP14 is two-step ownership rather than social recovery—the recovery standard is LSP11. The stated "LSP4 + LSP17 royalty enforcement" is also unsupported: LSP17 cannot intercept native LSP7/LSP8 transfer selectors to force marketplaces to pay royalties.

Verification

  • Reviewed all 55 changed files / 1,998 added lines.
  • yarn install --immutable: passed.
  • Prettier checks: passed.
  • Docusaurus production build: passed.
  • git diff --check: passed.
  • GitHub spellcheck and checks: passed.
  • The red build-and-deploy job is separate from these findings: deployment succeeded, then the PR-comment action failed with Resource not accessible by integration.

The overall direction is valuable, but the migration and authorization errors above are operationally unsafe and should be corrected before merge.

@JordyDutch JordyDutch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check automated report above on technical accuracy

@LUKSOAgent LUKSOAgent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed all 55 changed files on the current head, including the two follow-up commits. Formatting and the Docusaurus production build pass. The comments below are limited to high-confidence standards and migration-correctness issues. Several can result in an unusable recovery path or an unsafe migration, so I am requesting changes before merge.


Safe's "3 of 5" threshold doesn't exist directly in LSP6 — controllers are individual, not aggregated by a vote. Two patterns work:

- **Recovery contract** — deploy a contract that enforces the threshold itself, and register that contract as the controller holding `EDITPERMISSIONS` on the profile. Day-to-day controllers handle daily operations; the recovery contract handles ownership-level changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDITPERMISSIONS cannot install a replacement controller that has no permission entry yet. LSP6 requires ADDCONTROLLER for new controllers and EDITPERMISSIONS for changing or removing existing ones. Please grant the recovery path both permissions, or explicitly document a pre-provisioned recovery-controller flow. See the LSP6 permission definitions.

Permission: CALL
AllowedCalls:
- target: 0x<token>, standard: LSP7, selector: transfer
Expires: app-defined session policy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core LSP6 has no controller-expiry field. LSP25 validityTimestamps limits an individual relay signature, not the controller's continuing authority. If this is only app-side policy, a direct call can bypass it. Please remove the on-chain time-window claim or identify the additional on-chain verifier/module that enforces it.


### EIP-7702 set-code delegation

An EOA delegates to a smart-account implementation for the duration of a transaction, pairing with a paymaster for gasless flows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This describes an earlier EIP-7702 design. Final EIP-7702 delegation persists until it is replaced or cleared, and a different outer transaction sender can sponsor gas without inherently requiring an ERC-4337 bundler/paymaster stack. Please align this section with the final EIP and with line 13 of this page.


## How LSP8 and LSP4 make metadata a verifiable graph

LSP8 assets store per-token metadata through [ERC725Y](../../../standards/erc725.md) data keys instead of a single `tokenURI` string. LSP4 defines the metadata conventions — name, symbol, JSON schema — and its `VerifiableURI` type lets a key point to an off-chain payload _with a hash_, so the chain enforces what that off-chain blob is required to contain. Apps read `getDataForTokenId(tokenId, key)` and get a typed value back directly. Dynamic updates become `setData` calls, gated by whatever account permissions the issuer wants to configure. The metadata stops being a URL to trust and becomes a typed key-value graph you can verify.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A VerifiableURI is client-verifiable, not chain-enforced: the chain does not fetch or validate the remote JSON. getDataForTokenId returns raw bytes, and per-token updates use setDataForTokenId, whose standard authorization is ownership-based unless an LSP6-controlled ownership setup is added. Please correct those distinctions. See LSP2 VerifiableURI and LSP8 token metadata.

| Criterion | Ethereum L1 | Base | Polygon | Solana | LUKSO |
| ------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Secondary liquidity | deepest | fastest-growing EVM | strong enterprise mints | deepest non-EVM | early |
| Royalty enforcement | [ERC-2981](https://eips.ethereum.org/EIPS/eip-2981) signal | signal | signal | enforced on compressed NFTs | signal via [LSP4](../../../standards/tokens/LSP4-Digital-Asset-Metadata.md) metadata — at parity with ERC-2981, marketplace-discretionary like everywhere else on the EVM |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LSP4 defines digital-asset metadata, but it does not define a royalty recipient/rate signal equivalent to ERC-2981. Royalty signaling belongs to LSP18. Please reference LSP18 and state its current support status instead of claiming parity through LSP4.

| Grant call | `approve(spender, amount)` | `authorizeOperator(operator, amount, data)` |
| Common default | dApps request max-uint to skip re-prompting | still amount-scoped by convention — not fixed by LSP7 alone |
| Who can grant new approvals | anyone holding the private key, forever | only a controller whose LSP6 permissions allow calling `authorizeOperator` — revocable at the account level |
| Revoking an existing allowance | separate `approve(spender, 0)` transaction, per token | separate `revokeOperator(operator, tokenOwner)` transaction, per token — same shape as ERC-20, LSP7 doesn't change this |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current LSP7 ABI has four arguments: revokeOperator(operator, tokenOwner, notify, operatorNotificationData). The two-argument call shown here does not exist. Please include notify and the notification data, for example false, "0x". See the generated LSP7 API.


| Feature | ERC1155 | LSP7 + LSP8 |
| --------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Contracts per product | one | one per asset shape (typically two) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not generally two contracts. Each distinct fungible ERC-1155 asset needs its own LSP7 contract, plus an LSP8 collection for identifiable assets; otherwise independent balances are merged into one fungible asset. Please align this table and the tradeoff section with the corrected migration guide.


## Step 5 — sunset the old contract

Once the new contracts are live and every integration has migrated, pause the ERC1155 contract. Keep it deployed for historical reference — don't redeploy over it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERC-1155 does not define a standard pause function, so many existing deployments cannot perform this step. Snapshotting and minting before the old supply is frozen, burned, or escrowed also leaves both assets spendable. Please make pausing conditional and document an issuer-authorized cutover such as atomic pause/burn, escrow-and-claim, or another replay-protected migration.

| Transfer context payload | ❌ none — bolted on via wrapper contracts | ✅ native `bytes data` on every transfer |
| Authorization | `approve` / `allowance` / `transferFrom` | `authorizeOperator` — scoped, revocable, and notifies the operator |
| Metadata | `name()` / `symbol()` / `decimals()` only | ✅ unlimited [ERC725Y](../../../standards/erc725.md) key-value storage under [LSP4](../../../standards/tokens/LSP4-Digital-Asset-Metadata.md) |
| Accidental-transfer protection | ❌ none | ✅ `force` flag rejects transfers to non-receiving contracts by default |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

force is a required boolean argument and has no default. Transfers to EOAs or non-LSP1 contracts are rejected only when the caller explicitly passes force=false; force=true permits them. Please replace "by default" here and in the other LSP7/LSP8 comparisons.

@LUKSOAgent LUKSOAgent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final pass on ce77bd56: most earlier findings are fixed, including recovery permissions, EIP-7702 persistence and sponsorship, LSP8 metadata semantics, the LSP7 revokeOperator ABI, and per-asset ERC-1155 mapping. Five points remain in the inline comments. Local dependency install, Prettier, the Docusaurus production build, and git diff --check all pass. The new head's GitHub workflows are still awaiting approval.

Granting an app a session controller is one transaction. Revoking it is one transaction. The Universal Profile checks permissions on every call through [LSP20](../../../standards/accounts/lsp20-call-verification.md), so the scope is enforced on-chain, not at a wallet UI layer that a malicious or compromised frontend could bypass. Core LSP6 has no controller-expiry field — a controller's permissions stay live until someone explicitly revokes them in a transaction. An app can layer its own off-chain "session" convention on top (e.g. stop using a key after N hours), but that's an app-level policy, not something the account itself enforces; a direct call with that controller's key still works until the on-chain permission is actually revoked.

:::tip Ask for less, by default
A Universal Profile lets a dApp request exactly the permission it needs — one token, one function, one time window — instead of defaulting to full account access just because there was no standard way to ask for less.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 40 now correctly explains that core LSP6 has no controller expiry, but this tip still advertises one time window as part of the permission scope. LSP25 validity timestamps limit one relay signature, not the controller's continuing authority. Please remove this phrase or explicitly scope it to a single LSP25 relay.


- **Atomic pause + burn** — if your contract does have a pause/freeze modifier (common on OpenZeppelin-based deployments that added `Pausable`), pause it in the same transaction or block as the final snapshot, so nothing can transfer after the snapshot but before the new contracts go live.
- **Escrow-and-claim** — holders deposit their ERC1155 balance into an escrow or burn contract in exchange for the new LSP7/LSP8 tokens. This works even without a pause function, since it only relies on the standard `safeTransferFrom`, not owner-level control.
- **Replay-protected snapshot with a reconciliation window** — snapshot, then delay activating the new contracts' claim/mint path long enough to catch and reconcile any transfers that landed after the snapshot, with a documented policy for resolving disputes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A reconciliation window by itself is not replay protection. Delaying activation and reconciling interim transfers does not stop the legacy ERC-1155 balances from moving again after the new claims go live, so both representations can still be spent. Please remove this option or require a persistent on-chain cutoff or consume mechanism, such as claim-time escrow or burn, then also remove past the reconciliation window from the verification item.

| Criterion | Ethereum L1 | Base | Polygon | Solana | LUKSO |
| ------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Secondary liquidity | deepest | fastest-growing EVM | strong enterprise mints | deepest non-EVM | early |
| Royalty enforcement | [ERC-2981](https://eips.ethereum.org/EIPS/eip-2981) signal | signal | signal | enforced on compressed NFTs | **LSP18** — the named LUKSO royalty standard, but not yet fleshed out on-chain (no royalty events defined in the current spec); LSP4 itself carries no royalty field |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No royalty events does not mean LSP18 is not fleshed out on-chain. The current LSP-18 RFC already defines ERC725Y data keys for royalty recipients, percentages, and the enforcement hint; it does not itself enforce marketplace payment. Please describe it as an RFC metadata signal with marketplace-discretionary enforcement, and apply the same correction to creator-platforms.md.

| Transfer context payload | ❌ none — bolted on via wrapper contracts | ✅ native `bytes data` on every transfer |
| Authorization | `approve` / `allowance` / `transferFrom` | `authorizeOperator` — scoped, revocable, and notifies the operator |
| Metadata | `name()` / `symbol()` / `decimals()` only | ✅ unlimited [ERC725Y](../../../standards/erc725.md) key-value storage under [LSP4](../../../standards/tokens/LSP4-Digital-Asset-Metadata.md) |
| Accidental-transfer protection | ❌ none | ✅ required `force` flag — callers must explicitly pass `force=false` to reject transfers to non-receiving contracts |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

force is now correctly shown as required, but the false branch is still incomplete: force=false requires the recipient to be an LSP1 contract, so it rejects EOAs as well as non-LSP1 contracts; force=true permits either. Please state both cases here and mirror the fix in erc721-vs-lsp8.md and erc20-transfer-hooks.md.

Comment thread .github/workflows/ci.yml Outdated
runs-on: ubuntu-latest
needs: checks
permissions:
pull-requests: write

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this public-fork pull_request, GitHub downgrades requested write access to read-only. The previous run showed PullRequests: read and empty Cloudflare secrets, so this block does not fix the current comment or deploy path. Please revert the ineffective permission change or move only the comment into a separately secured trusted or proxy workflow that never executes fork code. If this block remains for same-repository PRs, also include contents: read, since unspecified permissions become none.

@tehnalogos
tehnalogos requested a review from LUKSOAgent July 23, 2026 13:50

@LUKSOAgent LUKSOAgent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the current head. All five remaining technical comments are correctly addressed. Approved.

@JordyDutch
JordyDutch merged commit 26bbedd into lukso-network:main Jul 23, 2026
2 of 3 checks passed
@JordyDutch

Copy link
Copy Markdown
Collaborator

Thanks a lot for this @tehnalogos! 🙏🏼

Merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants