Skip to main content

Deploy Contracts

Deploying a contract from the Universal Profile using the Browser Extension
Deploying a contract from a Universal Profile using the Browser Extension showing the bytecode of the contract

Setup​

npm install ethers

Deploy a contract from a UP​

The Universal Profile browser extension will magically wrap all the calls internally so you don't have to worry about crafting custom transactions. Simply use eth_sendTransaction as you always did while working with EOA.

import { ethers } from 'ethers';

const provider = new ethers.BrowserProvider(window.lukso);

await provider.send("eth_requestAccounts", []);

const signer = await provider.getSigner();
const account = await signer.getAddress();

// Send transaction
const tx = await signer.sendTransaction({
from: account, // The Universal Profile address
value: 0, // optional, in case constructor is payable
data: "0x608060405234801561001..." // Full bytecode of the contract to deploy + constructor args
});

const receipt = await tx.wait();

You can use the contract factory from ethers.js to deploy a contract by supplying its ABI and bytecode. For instance, if you're deploying an LSP7Mintable contract, you can obtain its ABI and bytecode from the @lukso/lsp-smart-contracts package.

npm install @lukso/lsp-smart-contracts

import LSP7Mintable from "@lukso/lsp-smart-contracts/artifacts/LSP7Mintable.json"

// Prepare the factory with the abi and bytecode
const lsp7Factory = new ethers.ContractFactory(
LSP7Mintable.abi,
LSP7Mintable.bytecode,
signer
);

// send the transaction of deployment
const transaction = await lsp7Factory.deploy(
'Yamen token',
'no desc',
"0x0000000000000000000000000000000000000011",
0,
false
);

const receipt = await transaction.waitForDeployment();

Retrieve Contract Deployment Infos​

There will be no contractAddress field in the transaction receipt unlike normal transactions happening with Metamask. This is because the UP browser extension wraps the deployment transaction within a normal contract execution transaction. The contract address can be calculated using the nonce and the address of the profile or retrieved using the logs (code snippet below).

import { ethers } from 'ethers';

const provider = new ethers.BrowserProvider(window.lukso);

await provider.send("eth_requestAccounts", []);

const signer = await provider.getSigner();
const account = await signer.getAddress();

// Send transaction
const tx = await signer.sendTransaction({
from: account, // The Universal Profile address
value: 0 // optional, in case constructor is payable
data: "0x0x608060405234801561001.." // Full bytecode of the contract to deploy + constructor args
});

const receipt = await tx.wait();

// The signature of the ContractCreated event emitted by the profile
/**
* event ContractCreated(
* uint256 indexed operationType,
* address indexed contractAddress,
* uint256 value,
* bytes32 indexed salt
* )
*
* 0xa1fb700aaee2ae4a2ff6f91ce7eba292f89c2f5488b8ec4c5c5c8150692595c3 = keccak256('ContractCreated(uint256,address,uint256,bytes32)')
*/
const targetTopic0 = '0xa1fb700aaee2ae4a2ff6f91ce7eba292f89c2f5488b8ec4c5c5c8150692595c3';

for (const log of receipt.logs) {
// Check if the first topic matches the target topic
if (log.topics[0] === targetTopic0) {

// Decode the operation type from bytes32 to number
const operationType = parseInt(log.topics[1], 16);

// Decode the contract address from bytes32 to Ethereum address
const contractAddress = `0x${log.topics[2].slice(-40)}`;

// Keep the salt as is
const salt = log.topics[3];

console.log('Found target event:');
console.log('Operation Type:', operationType);
console.log('Contract Address:', contractAddress);
console.log('Salt:', salt);
}
}

Examples​

Deploy an LSP7 Token​

import { ethers } from 'ethers';
import UniversalProfileArtifact from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';

// Path to generated artifact for your Custom Token Contract
import MyCustomToken from './path/to/artifacts/MyCustomToken.json';

// Connect to UP Browser Extension and get the UP address
const provider = new ethers.BrowserProvider(window.lukso);
await provider.send('eth_requestAccounts', []);
const account = await deployer.getAddress();

console.log('Deploying contract from Universal Profile: ', account);

// Create instance of the Universal Profile
const universalProfile = new ethers.Contract(UniversalProfileArtifact, account);

const abiEncoder = new ethers.AbiCoder();

// Encode constructor parameters
const encodedConstructorParams = abiEncoder.encode(
['string', 'string', 'address', 'uint256', 'bool'],
[
'My Custom Token', // token name
'MCT', // token symbol
accounts, // token owner (our UP)
0, // token type = TOKEN
false, // isNonDivisible?
],
);

// Add the constructor parameters to the token bytecode
const tokenBytecodeWithConstructor = ethers.concat([
MyCustomToken.bytecode,
encodedConstructorParams,
]);

// Get the address of the custom token contract that will be created
// https://docs.lukso.tech/contracts/contracts/ERC725/#execute
const customTokenAddress = await universalProfile.execute.staticCall(
1, // Operation type: CREATE
ethers.ZeroAddress, // Target: 0x0 as contract will be initialized
0, // Value is empty
tokenBytecodeWithConstructor, // Payload of the contract
);

// Deploy the contract by the Universal Profile
// https://docs.lukso.tech/contracts/contracts/ERC725/#execute
const tx = await universalProfile.execute(
1, // Operation type: CREATE
ethers.ZeroAddress, // Target: 0x0000...0000 as contract will be initialized
0, // Value is empty
tokenBytecodeWithConstructor, // Payload of the contract
);

// Wait for the transaction to be included in a block
await tx.wait();
console.log('πŸͺ™ Token contract deployed by πŸ†™ at: ', customTokenAddress);