Getting Started
@lukso/lsp-factory.js is a helper library for deploying Universal Profiles, LSP7 Digital Assets, and LSP8 Identifiable Digital Assets.
v4 uses viem for all blockchain interactions and deploys contracts atomically via LSP23LinkedContractsFactory.
Supported Networks
All contracts (LSP23 factory, base implementations) are deployed at the same deterministic addresses across chains via the Nick Factory (EIP-2470).
LSP23 Factory Address: 0x2300000A84D25dF63081feAa37ba6b62C4c89a30
| Network | Chain ID |
|---|---|
| LUKSO Mainnet | 42 |
| LUKSO Testnet | 4201 |
| Ethereum Mainnet | 1 |
| BASE | 8453 |
Installation
npm install @lukso/lsp-factory.js @erc725/erc725.js
Setup
Create a viem PublicClient (for reading) and WalletClient (for signing transactions), then pass them to LSPFactory:
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { luksoTestnet } from 'viem/chains';
import { LSPFactory } from '@lukso/lsp-factory.js';
const account = privateKeyToAccount('0x...');
const publicClient = createPublicClient({
chain: luksoTestnet,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: luksoTestnet,
transport: http(),
});
const factory = new LSPFactory(publicClient, walletClient);
You can use any viem-supported chain. Just swap luksoTestnet for the chain you want to deploy to:
import { lukso } from 'viem/chains'; // LUKSO Mainnet (42)
import { luksoTestnet } from 'viem/chains'; // LUKSO Testnet (4201)
import { mainnet } from 'viem/chains'; // Ethereum (1)
import { base } from 'viem/chains'; // BASE (8453)
Deploying a Universal Profile
Deploys an LSP0 Universal Profile and LSP6 KeyManager atomically via LSP23 Factory, then configures controller permissions and a Universal Receiver Delegate.
const contracts = await factory.UniversalProfile.deploy({
controllerAddresses: ['0x...'], // Addresses that will control the UP
});
console.log('UP Address:', contracts.LSP0ERC725Account.address);
console.log('KeyManager Address:', contracts.LSP6KeyManager.address);
With LSP3 metadata and a deterministic salt
First, encode your LSP3Profile metadata using erc725.js:
import { ERC725 } from '@erc725/erc725.js';
import LSP3ProfileSchema from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json';
const erc725 = new ERC725(LSP3ProfileSchema);
const encoded = erc725.encodeData([
{
keyName: 'LSP3Profile',
value: { json: metadata, url: 'ipfs://...' },
},
]);
const lsp3DataValue = encoded.values[0];
Then pass it into the deploy call:
const contracts = await factory.UniversalProfile.deploy(
{
controllerAddresses: ['0x...'],
lsp3DataValue, // Pre-encoded LSP3Profile VerifiableURI
},
{
salt: '0x...', // bytes32 salt for deterministic address generation
},
);
With custom controller permissions
import { ERC725 } from '@erc725/erc725.js';
// Replace with the correct addresses for your controllers
const adminController = '0x...';
const restrictedController = '0x...';
const contracts = await factory.UniversalProfile.deploy({
controllerAddresses: [
// Gets ALL_PERMISSIONS by default
adminController,
{
address: restrictedController,
permissions: ERC725.encodePermissions({ SUPER_SETDATA: true }),
},
],
});
Pre-computing addresses
Compute the UP and KeyManager addresses before deploying:
const { upAddress, keyManagerAddress } =
await factory.UniversalProfile.computeAddress(
{ controllerAddresses: ['0x...'] },
{ salt: '0x...' }, // Use the same salt you will deploy with
);
Deploying an LSP7 Digital Asset
Deploys an LSP7 Digital Asset (fungible token) as a minimal proxy:
const contracts = await factory.LSP7DigitalAsset.deploy({
name: 'My Token',
symbol: 'MTK',
controllerAddress: '0x...', // Owner of the token contract
tokenType: 0, // 0 = Token, 1 = NFT, 2 = Collection
isNFT: false, // Whether the token is non-divisible
});
console.log('LSP7 Address:', contracts.LSP7DigitalAsset.address);
With metadata
const contracts = await factory.LSP7DigitalAsset.deploy({
name: 'My Token',
symbol: 'MTK',
controllerAddress: '0x...',
tokenType: 0,
isNFT: false,
digitalAssetMetadata: {
verification: {
method: 'keccak256(bytes)',
data: '0x...', // keccak256 hash of the JSON metadata file bytes
},
url: 'ipfs://Qm...',
},
});
Deploying an LSP8 Identifiable Digital Asset
Deploys an LSP8 Identifiable Digital Asset (NFT) as a minimal proxy:
const contracts = await factory.LSP8IdentifiableDigitalAsset.deploy({
name: 'My NFT Collection',
symbol: 'MNFT',
controllerAddress: '0x...',
tokenType: 1, // 0 = Token, 1 = NFT, 2 = Collection
// Token ID format constants (from @lukso/lsp8-contracts):
// 0 = UNIQUE_ID (unique bytes32)
// 1 = NUMBER (sequential uint256)
// 2 = STRING (human-readable string)
// 3 = ADDRESS (address packed in bytes32)
// 4 = HASH (keccak256 hash)
tokenIdFormat: 1,
});
console.log('LSP8 Address:', contracts.LSP8IdentifiableDigitalAsset.address);
Deployment Events
All deploy methods accept an onDeployEvents callback for tracking deployment progress:
const contracts = await factory.UniversalProfile.deploy(
{ controllerAddresses: ['0x...'] },
{
onDeployEvents: {
next: (event) => {
console.log(event.status, event.contractName, event.functionName);
},
error: (error) => {
console.error('Deployment error:', error);
},
complete: (deployedContracts) => {
console.log('Deployment complete:', deployedContracts);
},
},
},
);
Next steps
- Edit Universal Profile metadata
- Deploying tokens and NFTs
- LSP1 Notification Type IDs — understanding notifications when deploying assets