diff --git a/digital-assets/tokenIdFormat.ts b/digital-assets/tokenIdFormat.ts new file mode 100644 index 0000000..79de742 --- /dev/null +++ b/digital-assets/tokenIdFormat.ts @@ -0,0 +1,98 @@ +import { ethers } from 'ethers'; +import lsp8Artifact from '@lukso/lsp-smart-contracts/artifacts/LSP8IdentifiableDigitalAsset.json'; +import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts'; + +const SAMPLE_LSP8_ASSET = '0x8734600968c7e7193BB9B1b005677B4edBaDcD18'; +const RPC_URL = 'https://rpc.testnet.lukso.gateway.fm'; + +const provider = new ethers.JsonRpcProvider(RPC_URL); + +// Create contract instance +const myAssetContract = new ethers.Contract( + SAMPLE_LSP8_ASSET, + lsp8Artifact.abi, + provider, +); + +// Sample Token ID of the contract +// Could be 1, my-token-id, 0x123, etc. +const myTokenId = '1'; + +// https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidformat +async function getTokenIdFormat() { + try { + const tokenIdFormat = parseInt( + // https://docs.lukso.tech/tools/erc725js/classes/ERC725#getdata + await myAssetContract.getData(ERC725YDataKeys.LSP8['LSP8TokenIdFormat']), + 16, + ); + return tokenIdFormat; + } catch (err) { + console.error( + 'Could not retrieve LSP8TokenIdFormat. Please provide an LSP8 asset address.', + ); + return null; + } +} + +// Get the global token ID format of the asset +let tokenIdFormat = await getTokenIdFormat(); +console.log('Global ID Format: ', tokenIdFormat); + +// Convert a token ID according to LSP8TokenIdFormat +// https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidformat +const convertTokenId = (tokenID: string, tokenIdFormat: number) => { + switch (tokenIdFormat) { + // TokenID is Number + case 0: + case 100: + // uint256 - Number (Left padded) + return ethers.zeroPadValue('0x0' + BigInt(tokenID).toString(16), 32); + // TokenID is String + case 1: + case 101: + // string - String (Right padded) + return ethers.encodeBytes32String(tokenID).padEnd(32, '0'); + // TokenID is Address + case 2: + case 102: + // address - Smart Contract (Left padded) + return ethers.zeroPadValue(tokenID, 32); + // TokenID is Byte Value + case 3: + case 103: + // bytes32 - Unique Bytes (Right padded) + return ethers + .hexlify(ethers.getBytes(tokenID).slice(0, 32)) + .padEnd(66, '0'); + // TokenID is Hash Digest + case 4: + case 104: + // bytes32 - Hash Digest (No padding) + return tokenID; + } +}; + +// Convert the token ID based on the token ID format +if (tokenIdFormat !== null) { + let byte32TokenId = convertTokenId(myTokenId, tokenIdFormat); + console.log('Parsed Byte32 Token ID: ', byte32TokenId); + + // If token ID format is mixed + if (tokenIdFormat >= 100) { + tokenIdFormat = parseInt( + // Retrieve token ID format for the individual token ID + // https://docs.lukso.tech/contracts/contracts/LSP8IdentifiableDigitalAsset/#getdatafortokenid + await myAssetContract.getDataForTokenId( + byte32TokenId, + ERC725YDataKeys.LSP8['LSP8TokenIdFormat'], + ), + 16, + ); + // Convert the token ID based on the individual token ID format + byte32TokenId = convertTokenId(myTokenId, tokenIdFormat); + console.log('Individual Token ID Format: ', tokenIdFormat); + } else { + console.log('Asset has a global Token ID Format'); + } +} diff --git a/smart-contracts-hardhat/global.ts b/smart-contracts-hardhat/global.ts new file mode 100644 index 0000000..6d6889c --- /dev/null +++ b/smart-contracts-hardhat/global.ts @@ -0,0 +1,8 @@ +export const LSP23_FACTORY_ADDRESS_TESTNET = '0x2300000A84D25dF63081feAa37ba6b62C4c89a30'; +export const LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET = + '0x000000000066093407b6704B89793beFfD0D8F00'; +export const UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET = + '0x3024D38EA2434BA6635003Dc1BDC0daB5882ED4F'; +export const LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS_TESTNET = + '0x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4'; +export const LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET = '0x7870C5B8BC9572A8001C3f96f7ff59961B23500D'; diff --git a/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts b/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts index 417c2ee..5155904 100644 --- a/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts +++ b/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts @@ -62,7 +62,7 @@ async function deployTokenWithMetadata() { // Create the transaction payload for setting storage data // https://docs.lukso.tech/contracts/contracts/ERC725/#setdata - const lsp4StorageBytecode = token.interface.encodeFunctionData('setData', [ + const setLSP4MetadataPayload = token.interface.encodeFunctionData('setData', [ metadataKey, encodedLSP4Metadata.values[0], ]); @@ -82,7 +82,7 @@ async function deployTokenWithMetadata() { [0, 0], // Value is empty for both operations [ tokenBytecodeWithConstructor, // Payload for contract deployment - lsp4StorageBytecode, // Payload for setting a data key on the deployed contract + setLSP4MetadataPayload, // Payload for setting a data key on the deployed contract ], ); diff --git a/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts b/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts new file mode 100644 index 0000000..af8d5f4 --- /dev/null +++ b/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts @@ -0,0 +1,156 @@ +// libs +import { ethers } from 'hardhat'; +import { AbiCoder } from 'ethers'; +import { ERC725 } from '@erc725/erc725.js'; + +// LSPs Smart Contracts artifacts + +import LSP23FactoryArtifact from '@lukso/lsp-smart-contracts/artifacts/LSP23LinkedContractsFactory.json'; +import UniversalProfileInitArtifact from '@lukso/lsp-smart-contracts/artifacts/UniversalProfileInit.json'; + +// ERC725.js Metadata schemas + +import LSP1UniversalReceiverDelegateSchemas from '@erc725/erc725.js/schemas/LSP1UniversalReceiverDelegate.json'; +import LSP3ProfileMetadataSchemas from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json'; +import LSP6KeyManagerSchemas from '@erc725/erc725.js/schemas/LSP6KeyManager.json'; + +// Constants +import { + LSP23_FACTORY_ADDRESS_TESTNET, + UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET, + LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET, + LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS_TESTNET, + LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET, +} from '../global'; + +// Constants that needs to be overwritten +const MAIN_CONTROLLER_EOA = '0x3303Ce3b8644D566271DD2Eb54292d32F1458968'; +const SALT = '0x5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed'; + +async function main() { + // Interacting with the LSP23Factory contract + const lsp23FactoryContract = await ethers.getContractAtFromArtifact( + LSP23FactoryArtifact, + LSP23_FACTORY_ADDRESS_TESTNET, + ); + + // Interacting with the UniversalProfileImplementation contract + const universalProfileImplementationContract = await ethers.getContractAtFromArtifact( + UniversalProfileInitArtifact, + UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET, + ); + + // create the init structs of LSP23 Linked Contracts Factory + const universalProfileInitStruct = { + salt: SALT, + fundingAmount: 0, + implementationContract: UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET, + initializationCalldata: universalProfileImplementationContract.interface.encodeFunctionData( + 'initialize', + [LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET], + ), // this will call the `initialize(...)` function of the Universal Profile and set the LSP23_POST_DEPLOYMENT_MODULE as `owner()` + }; + + const keyManagerInitStruct = { + fundingAmount: 0, + implementationContract: LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS_TESTNET, + addPrimaryContractAddress: true, // this will append the primary contract address to the init calldata + initializationCalldata: '0xc4d66de8', // `initialize(address)` function selector + extraInitializationParams: '0x', + }; + + // instantiate the erc725.js class + const erc725 = new ERC725([ + ...LSP6KeyManagerSchemas, + ...LSP3ProfileMetadataSchemas, + ...LSP1UniversalReceiverDelegateSchemas, + ]); + + // create the LSP3Metadata data value + const lsp3DataValue = { + verification: { + method: 'keccak256(utf8)', + data: '0x6d6d08aafb0ee059e3e4b6b3528a5be37308a5d4f4d19657d26dd8a5ae799de0', + }, + url: 'ipfs://QmPRoJsaYcNqQiUrQxE7ajTRaXwHyAU29tHqYNctBmK64w', // this is an example of Metadata stored on IPFS + }; + + // create the permissions data keys - value pairs to be set + const setDataKeysAndValues = erc725.encodeData([ + { keyName: 'LSP3Profile', value: lsp3DataValue }, // LSP3Metadata data key and value + { + keyName: 'LSP1UniversalReceiverDelegate', + value: LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET, + }, // Universal Receiver data key and value + { + keyName: 'AddressPermissions:Permissions:
', + dynamicKeyParts: [LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET], + value: erc725.encodePermissions({ + REENTRANCY: true, + SUPER_SETDATA: true, + }), + }, // Universal Receiver Delegate permissions data key and value + { + keyName: 'AddressPermissions:Permissions:
', + dynamicKeyParts: [MAIN_CONTROLLER_EOA], + value: erc725.encodePermissions({ + CHANGEOWNER: true, + ADDCONTROLLER: true, + EDITPERMISSIONS: true, + ADDEXTENSIONS: true, + CHANGEEXTENSIONS: true, + ADDUNIVERSALRECEIVERDELEGATE: true, + CHANGEUNIVERSALRECEIVERDELEGATE: true, + REENTRANCY: false, + SUPER_TRANSFERVALUE: true, + TRANSFERVALUE: true, + SUPER_CALL: true, + CALL: true, + SUPER_STATICCALL: true, + STATICCALL: true, + SUPER_DELEGATECALL: false, + DELEGATECALL: false, + DEPLOY: true, + SUPER_SETDATA: true, + SETDATA: true, + ENCRYPT: true, + DECRYPT: true, + SIGN: true, + EXECUTE_RELAY_CALL: true, + }), // Main Controller permissions data key and value + }, + // length of the Address Permissions array and their respective indexed keys and values + { + keyName: 'AddressPermissions[]', + value: [LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET, MAIN_CONTROLLER_EOA], + }, + ]); + + const abiCoder = new AbiCoder(); + const types = ['bytes32[]', 'bytes[]']; // types of the parameters + + const initializeEncodedBytes = abiCoder.encode(types, [ + setDataKeysAndValues.keys, + setDataKeysAndValues.values, + ]); + + // deploy the Universal Profile and its Key Manager + const [upAddress, keyManagerAddress] = await lsp23FactoryContract.deployERC1167Proxies.staticCall( + universalProfileInitStruct, + keyManagerInitStruct, + LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET, + initializeEncodedBytes, + ); + console.log('Universal Profile address:', upAddress); + console.log('Key Manager address:', keyManagerAddress); + + const tx = await lsp23FactoryContract.deployERC1167Proxies( + universalProfileInitStruct, + keyManagerInitStruct, + LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET, + initializeEncodedBytes, + ); + await tx.wait(); +} + +main();