We can create a Dao contract for an user using the DaoRegister contract deployed by Relation Protocol. A dao contract can provide a Dao with member management functions. Both the DaoRegister contract and the Dao contract implement the contract interface defined by the Contract Standard.
Construct a Contract object
Via Relation Protocol's resource list, you can acquire the contract address and abi file of DaoRegister and DaoWithSign, and the abi file of Dao contract. As for the address of a Dao contract, you need to query it through DaoRegister.
The administrator of a Dao can assign description and avatar for a Dao, with the description and avatar stored on Arweave. We use the data format for this as follows:
{
"avatar": "${The avatar of DAO}",
"description": "${The description of DAO}"
}
The transaction hash of the upload will be stored in the contract as DaoURI
You can get a whole list of Dao members with a traversal process to find all the token owners of said Dao.
const daoContractAddress = '0x000...';
const daoContract = getDaoContractInstance(daoContractAddress)
const memberCount = await daoContractAddress.totalSupply();
var memberList = [];
for(var i = 0; i < memberCount;i++){
const tokenId = await daoContract.tokenByIndex(i);
const member = await daoContract.ownerOf(tokenId);
memberList.push(member);
}
Configure the DaoURI(Gas fee can be paid by someone else)
The administrator of a Dao can assign description and avatar to a Dao, with the content stored on Arweave. The format of the content should be:
{
"avatar": "${The avatar of DAO}",
"description": "${The description of DAO}"
}
The administrator signs against the transaction hash and constructs a parameter to be posted on the blockchain. Any address can use this parameter to initiate a transaction on the blockchain, with the gas fee paid by said address.
import { Bytes } from '@ethersproject/bytes'
const daoURI = 'hX_Mne1...';
const daoContractAddress = '0x000...';
const daoContract = getDaoContractInstance(daoContractAddress)
const daoWithSignContract = getDaoWithSignContractInstance(daoContractAddress)
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
let name = await daoWithSignContract.name();
let nonce = await daoWithSignContract.nonces(accounts[0]);
//The time when the signature expires(Unit: second). The following example means the signature will expire 100 seconds after the current time.
let deadline = Date.parse(new Date()) / 1000 + 100;
let sign = await getSign(await buildSetDaoURIParam(
name,
daoWithSignContract.address.toLowerCase(),
daoContract.address.toLowerCase(),
daoURI,
parseInt(nonce),
deadline),
accounts[0]);
let param = {
"sig": {"v": sign.v, "r": sign.r, "s": sign.s, "deadline": deadline},
"target": daoContract.address,
"addr": accounts[0],
"daoURI": daoURI
}
//In reality, this method will be called by the address paying the gas fee.
await daoWithSignContract.connect(accounts[1]).setDaoURIWithSign(param);
async function getSign(msgParams, signerAddress) {
const params = [signerAddress, msgParams];
const trace = await hre.network.provider.send(
"eth_signTypedData_v4", params);
return Bytes.splitSignature(trace);
}
async function getChainId() {
return await ethereum.request({
method: 'eth_chainId',
});
}
async function buildSetDaoURIParam(name, contractAddress,daoContractAddress, daoURI, nonce, deadline) {
return {
domain: {
chainId: await getChainId(),
name: name,
verifyingContract: contractAddress,
version: '1',
},
// Defining the message signing data content.
message: {
target: daoContractAddress,
daoURI: daoURI,
nonce: nonce,
deadline: deadline,
},
// Refers to the keys of the *types* object below.
primaryType: 'SetDaoURIWithSign',
types: {
EIP712Domain: [
{name: 'name', type: 'string'},
{name: 'version', type: 'string'},
{name: 'chainId', type: 'uint256'},
{name: 'verifyingContract', type: 'address'},
],
SetDaoURIWithSign: [
{name: 'target', type: 'address'},
{name: 'daoURI', type: 'string'},
{name: 'nonce', type: 'uint256'},
{name: 'deadline', type: 'uint256'},
],
},
};
}
When an administrator adds a member to a Dao(Gas fee can be paid by someone else)
An administrator can add specific addresses to a Dao.
import { Bytes } from '@ethersproject/bytes'
const daoContractAddress = '0x000...';
const daoContract = getDaoContractInstance(daoContractAddress)
const daoWithSignContract = getDaoWithSignContractInstance(daoContractAddress)
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
const members = ['0x001...','0x002...','0x003...'];
let name = await daoWithSignContract.name();
let nonce = await daoWithSignContract.nonces(accounts[0]);
//The time when the signature expires(Unit: second). The following example means the signature will expire 100 seconds after the current time.
let deadline = Date.parse(new Date()) / 1000 + 100;
let sign = await getSign(await buildAddMemberParam(
name,
daoWithSignContract.address.toLowerCase(),
daoContract.address.toLowerCase(),
members,
parseInt(nonce),
deadline),
accounts[0]);
let param = {
"sig": {"v": sign.v, "r": sign.r, "s": sign.s, "deadline": deadline},
"target": daoContract.address,
"addr": accounts[0],
"members": members
}
//In reality, this method will be called by the address paying the gas fee.
await expect(daoWithSignContract.connect(accounts[1]).addMemberWithSign(param));
async function getSign(msgParams, signerAddress) {
const params = [signerAddress, msgParams];
const trace = await hre.network.provider.send(
"eth_signTypedData_v4", params);
return Bytes.splitSignature(trace);
}
async function getChainId() {
return await ethereum.request({
method: 'eth_chainId',
});
}
async function buildAddMemberParam(name, contractAddress, daoContractAddress,members, nonce, deadline) {
return {
domain: {
chainId: await getChainId(),
name: name,
verifyingContract: contractAddress,
version: '1',
},
// Defining the message signing data content.
message: {
target: daoContractAddress,
members: members,
nonce: nonce,
deadline: deadline,
},
// Refers to the keys of the *types* object below.
primaryType: 'AddMemberWithSign',
types: {
EIP712Domain: [
{name: 'name', type: 'string'},
{name: 'version', type: 'string'},
{name: 'chainId', type: 'uint256'},
{name: 'verifyingContract', type: 'address'},
],
AddMemberWithSign: [
{name: 'target', type: 'address'},
{name: 'members', type: 'address[]'},
{name: 'nonce', type: 'uint256'},
{name: 'deadline', type: 'uint256'},
],
},
};
}
Open Access(Gas fee can be paid by someone else)
An administrator of a Dao can set it to Open Access, meaning that anyone can join the Dao.
import { Bytes } from '@ethersproject/bytes'
const daoContractAddress = '0x000...';
const daoContract = getDaoContractInstance(daoContractAddress)
const daoWithSignContract = getDaoWithSignContractInstance(daoContractAddress)
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
const isFreeJoin = true;
let name = await daoWithSignContract.name();
let nonce = await daoWithSignContract.nonces(accounts[0]);
let deadline = Date.parse(new Date()) / 1000 + 100;
let sign = await getSign(await buildSetFreeJoinParam(
name,
daoWithSignContract.address.toLowerCase(),
daoContract.address.toLowerCase(),
isFreeJoin,
parseInt(nonce),
deadline),
accounts[0]);
let param = {
"sig": {"v": sign.v, "r": sign.r, "s": sign.s, "deadline": deadline},
"target": daoContract.address,
"addr": accounts[0],
"isFreeJoin": isFreeJoin
}
//In reality, this method will be called by the address paying the gas fee.
await daoWithSignContract.connect(accounts[1]).setFreeJoinWithSign(param);
async function getSign(msgParams, signerAddress) {
const params = [signerAddress, msgParams];
const trace = await hre.network.provider.send(
"eth_signTypedData_v4", params);
return Bytes.splitSignature(trace);
}
async function getChainId() {
return await ethereum.request({
method: 'eth_chainId',
});
}
async function buildSetFreeJoinParam(name, contractAddress, daoContractAddress,isFreeJoin, nonce, deadline) {
return {
domain: {
chainId: await getChainId(),
name: name,
verifyingContract: contractAddress,
version: '1',
},
// Defining the message signing data content.
message: {
target: daoContractAddress,
isFreeJoin: isFreeJoin,
nonce: nonce,
deadline: deadline,
},
// Refers to the keys of the *types* object below.
primaryType: 'SetFreeJoinWithSign',
types: {
EIP712Domain: [
{name: 'name', type: 'string'},
{name: 'version', type: 'string'},
{name: 'chainId', type: 'uint256'},
{name: 'verifyingContract', type: 'address'},
],
SetFreeJoinWithSign: [
{name: 'target', type: 'address'},
{name: 'isFreeJoin', type: 'bool'},
{name: 'nonce', type: 'uint256'},
{name: 'deadline', type: 'uint256'},
],
},
};
}
When an user joins a Dao(Gas fee can be paid by someone else)
With Open Access enabled by the administrator of a Dao, users can join the Dao freely.
import { Bytes } from '@ethersproject/bytes'
const daoContractAddress = '0x000...';
const daoContract = getDaoContractInstance(daoContractAddress)
const daoWithSignContract = getDaoWithSignContractInstance(daoContractAddress)
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
let name = await daoWithSignContract.name();
let nonce = await daoWithSignContract.nonces(accounts[0]);
let deadline = Date.parse(new Date()) / 1000 + 100;
let sign = await getSign(await buildJoinParam(
name,
daoWithSignContract.address.toLowerCase(),
daoContract.address.toLowerCase(),
parseInt(nonce),
deadline),
accounts[0]);
let param = {
"sig": {"v": sign.v, "r": sign.r, "s": sign.s, "deadline": deadline},
"target": daoContract.address,
"addr": accounts[0]
}
//In reality, this method will be called by the address paying the gas fee.
await expect(daoWithSignContract.connect(accounts[1]).joinWithSign(param))
async function getSign(msgParams, signerAddress) {
const params = [signerAddress, msgParams];
const trace = await hre.network.provider.send(
"eth_signTypedData_v4", params);
return Bytes.splitSignature(trace);
}
async function getChainId() {
return await ethereum.request({
method: 'eth_chainId',
});
}
async function buildJoinParam(name, contractAddress, daoContractAddress,nonce, deadline) {
return {
domain: {
chainId: await getChainId(),
name: name,
verifyingContract: contractAddress,
version: '1',
},
// Defining the message signing data content.
message: {
target: daoContractAddress,
nonce: nonce,
deadline: deadline,
},
// Refers to the keys of the *types* object below.
primaryType: 'JoinWithSign',
types: {
EIP712Domain: [
{name: 'name', type: 'string'},
{name: 'version', type: 'string'},
{name: 'chainId', type: 'uint256'},
{name: 'verifyingContract', type: 'address'},
],
JoinWithSign: [
{name: 'target', type: 'address'},
{name: 'nonce', type: 'uint256'},
{name: 'deadline', type: 'uint256'},
],
},
};
}
Remove a Dao member(Gas fee can be paid by someone else)
An administrator can remove a member from a Dao. Also, users can leave a Dao freely.
import { Bytes } from '@ethersproject/bytes'
const daoContractAddress = '0x000...';
const daoContract = getDaoContractInstance(daoContractAddress)
const daoWithSignContract = getDaoWithSignContractInstance(daoContractAddress)
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
let name = await daoWithSignContract.name();
let nonce = await daoWithSignContract.nonces(accounts[0]);
let deadline = Date.parse(new Date()) / 1000 + 100;
let sign = await getSign(await buildRemoveParam(
name,
daoWithSignContract.address.toLowerCase(),
daoContract.address.toLowerCase(),
accounts[0].toLowerCase(),
parseInt(nonce),
deadline),
accounts[0]);
let param = {
"sig": {"v": sign.v, "r": sign.r, "s": sign.s, "deadline": deadline},
"target": daoContract.address,
"addr": accounts[0],
"member": accounts[0]
}
//In reality, this method will be called by the address paying the gas fee.
await expect(daoWithSignContract.connect(accounts[1]).removeWithSign(param))
async function getSign(msgParams, signerAddress) {
const params = [signerAddress, msgParams];
const trace = await hre.network.provider.send(
"eth_signTypedData_v4", params);
return Bytes.splitSignature(trace);
}
async function getChainId() {
return await ethereum.request({
method: 'eth_chainId',
});
}
async function buildRemoveParam(name, contractAddress, daoContractAddress,member, nonce, deadline) {
return {
domain: {
chainId: await getChainId(),
name: name,
verifyingContract: contractAddress,
version: '1',
},
// Defining the message signing data content.
message: {
target: daoContractAddress,
member: member,
nonce: nonce,
deadline: deadline,
},
// Refers to the keys of the *types* object below.
primaryType: 'RemoveWithSign',
types: {
EIP712Domain: [
{name: 'name', type: 'string'},
{name: 'version', type: 'string'},
{name: 'chainId', type: 'uint256'},
{name: 'verifyingContract', type: 'address'},
],
RemoveWithSign: [
{name: 'target', type: 'address'},
{name: 'member', type: 'address'},
{name: 'nonce', type: 'uint256'},
{name: 'deadline', type: 'uint256'},
],
},
};
}