Skip to main content
Version: V3

License Modifications

Licensing

Please note that Uniswap V3 is under BUSL license until the Change Date, currently 2023-04-01. Exceptions to the license may be specified by Uniswap Governance via Additional Use Grants, which can, for example, allow V3 to be deployed on new chains. Please follow the Uniswap Governance process to request a DAO vote for exceptions to the license, or to move up the Change Date.

License changes must be enacted via the ENS domain uniswap.eth, which is controlled by Uniswap Governance. This means (among other things) that Governance has the power to associate arbitrary text with any subdomain of the form X.uniswap.eth. Modifications of the Change Date should be specified at v3-core-license-date.uniswap.eth, and Additional Use Grants should be specified at v3-core-license-grants.uniswap.eth. The process for associating text with a subdomain is detailed below:

ENS Subdomain Details & Process

If the subdomain does not already exist which can be checked here, the setSubnodeRecord function of the ENS registry should be called with the following arguments:

  • node: namehash('uniswap.eth') (0xa2a03459171c76bff45817330c10ef9f8af07011a33005b73b50189bbc7e7132)
  • label: keccak256('v3-core-license-date') (0xee55740591b0fd5d7a28a6edc49567f6ff3febbe942ec0e2fa49ee536595085b) or keccak256('v3-core-license-grants') (0x15ff9b5bd7642701a10e5ea8fb29c957ffda4854cd028e9f6218506e6b509af2)
  • owner: 0x1a9C8182C09F50C8318d769245beA52c32BE35BC, the Uniswap Governance Timelock
  • resolver: 0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41, the public ENS resolver.
  • ttl: 0
  1. Then, the setText function of the public resolver should be called with the following arguments:
  • node: namehash('v3-core-license-date.uniswap.eth') (0x0505ec7822d61b4cfb294f137d1a7f0ceedf162f555a4bf2f4be58a07cf266c5) or namehash('v3-core-license-grants.uniswap.eth') (0xa35d592ec6e5289a387cba1d5f82be794f495bd5a361a1fb314687c6aefea1f4)
  • key: A suitable label, such as notice.
  • value: The text of the change. Note that text may already be associated with the subdomain in question. If it does, it can be reviewed at the following URLs for either v3-core-license-date or v3-core-license-grants, and appended to as desired.

Note: setContentHash may also be used to associate text with a subdomain, but setText is presented above for simplicity.

These contract function calls should then be encoded into a governance proposal, and approved by Uniswap Governance.

Proposals

Proposals are submitted via GovernorBravoDelegator @ 0x408ED6354d4973f66138C91495F2f2FCbd8724C3, a proxy contract currently pointing to the implementation at 0x53a328F4086d7C0F1Fa19e594c9b842125263026. NPM packages for consuming the governance contract ABIs, and details on previous versions, are available here.

Governor Bravo #propose Parameters
/**
* @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold
* @param targets Target addresses for proposal calls
* @param values Eth values for proposal calls
* @param signatures Function signatures for proposal calls
* @param calldatas Calldatas for proposal calls
* @param description String description of the proposal
* @return Proposal id of new proposal
*/
function propose(
address[] memory targets,
uint[] memory values,
string[] memory signatures,
bytes[] memory calldatas,
string memory description
) public returns (uint)

Populating Proposal Calldata

Below is an example of using a scripting environment to generate a proposal. This is for educational purposes only - that example assumes access to a private key with a sufficient amount of delegated UNI to submit a proposal, which is an insecure practice. There are several ways to generate a proposal transaction and submit it to Ethereum; this example should only be used for reference and not in production.

Populating `Propose` Calldata
import { Contract, ethers } from 'ethers'
import { namehash } from '@ethersproject/hash'
import { keccak256 } from '@ethersproject/keccak256'
import { Interface } from '@ethersproject/abi'
// note: contract ABIs should be imported via etherscan
import { GOVERNOR_BRAVO_ABI, ENS_REGISTRY_ABI, ENS_PUBLIC_RESOLVER_ABI } from './utils'

const GOVERNOR_BRAVO_ADDRESS: string = '0x408ED6354d4973f66138C91495F2f2FCbd8724C3'
const ENS_REGISTRY_ADDRESS: string = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'
const PUBLIC_ENS_RESOLVER_ADDRESS: string = '0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'
const UNISWAP_GOVERNANCE_TIMELOCK_ADDRESS: string = '0x1a9C8182C09F50C8318d769245beA52c32BE35BC'

const provider = new ethers.providers.JsonRpcProvider('YOUR_RPC_URL_HERE')
const signer = provider.getSigner('YOUR_SIGNER_ADDRESS_HERE')

// note: setting the subnode record should only take place if the subdomain does not already exist
const ensRegistryInterface = new Interface(ENS_REGISTRY_ABI)
const setSubnodeRecordCalldata = ensRegistryInterface.encodeFunctionData('setSubnodeRecord', [
// node: The parent node
namehash('uniswap.eth'),
// label: The hash of the label specifying the subnode
keccak256('v3-core-license-grants'),
// owner: The address of the new owner
UNISWAP_GOVERNANCE_TIMELOCK_ADDRESS,
// resolver: The address of the resolver
PUBLIC_ENS_RESOLVER_ADDRESS,
// ttl: The TTL, i.e., time to live, in seconds
0,
])

const ensPublicResolverInterface = new Interface(ENS_PUBLIC_RESOLVER_ABI)
const setTextCalldata = ensPublicResolverInterface.encodeFunctionData('setText', [
// node: The node to update
namehash('v3-core-license-grants.uniswap.eth'),
// key: The key to set
'[your-projects-additional-use-grant-title]',
// value: The text data value to set
'[your-additional-use-grant-description]',
])

// Create a new local instance of the governorBravo contract
// Note that in production the abi should be gathered via etherscan
const governorBravo = new Contract(GOVERNOR_BRAVO_ADDRESS, GOVERNOR_BRAVO_ABI, provider)

// the ordered list of target addresses for calls to be made
const targets = [ENS_REGISTRY_ADDRESS, PUBLIC_ENS_RESOLVER_ADDRESS]

// The ordered list of values to be passed to the calls to be made. i.e., the amount of
// ETH values to be transferred within the transaction. as this example does not include
// the transferring of any ETH, this list is empty.
const values = [0, 0]

// The ordered list of function signatures to be called. The signatures arguments
// are optional, if not provided, the function signature will be inferred from the calldata
const signatures = ['', '']

// The ordered list of calldata to be passed to each call in the proposal. The calldata
// in this example takes the place of the function signature arguments.
const calldatas = [setSubnodeRecordCalldata, setTextCalldata]

// the description of the proposal.
const description = '# TITLE ## SECTION_EXPLANATION'

async function main() {
try {
const txResponse: ethers.providers.TransactionResponse = await governorBravo
.connect(signer)
.propose(targets, values, signatures, calldatas, description)
console.log(`Proposal transaction sent: ${txResponse.hash}`)
await txResponse.wait(1)
console.log(
`Proposal has been mined at blocknumber: ${txResponse.blockNumber}, transaction hash: ${txResponse.hash}`
)
} catch (error) {
console.error(error)
}
}

main().then(() => console.log('done'))