diff --git a/docs/guides/colony-creation.md b/docs/guides/colony-creation.md index f10492b6..f4e3a887 100644 --- a/docs/guides/colony-creation.md +++ b/docs/guides/colony-creation.md @@ -17,38 +17,33 @@ For a full example see [here](https://github.com/JoinColony/colonySDK/blob/main/ These examples assume that the user executing the transactions has funds in their wallet to pay for gas. If you'd like to use gasless transactions instead, use `metaTx()` instead of `tx()`. ::: -## Step 1 (optional) - Creating a token +## Step 1 - Deploying the Colony contract (and optionally its token) -If you don't have a token contract deployed yet that you wish to use as the native token in your colony, deploying it using the Colony Network is highly recommended. That will give you a better integration and control over your token from your colony (e.g. using the `mintTokens` function on the colony). - -To deploy a token, call the `deployToken` method on `ColonyNetwork`: +The most important step. Here the actualy colony contract will be deployed. This happens by executing a contract method on the `ColonyNetwork` (as opposed to a deploy-transaction): ```typescript -// Create token to be used with Colony -const [{ tokenAddress }] = await colonyNetwork - .deployToken('Test token', 'TOT') +// Create actual colony (deploys Colony contract) +const [{ colonyAddress, tokenAddress, tokenAuthorityAddress }] = await colonyNetwork + .createColony({ name: 'Test token', symbol: 'TOT' }, 'colonytestname') .tx(); -console.info('Token address', tokenAddress); ``` One can specify the token name, its symbol and even its decimals (even though it's recommended to leave that at the default value). This will deploy a special ERC20 token that integrates well with Colony (e.g. it supports permissions, minting and gasless transactions out of the box). For its contract code see [here](https://github.com/JoinColony/colonyNetwork/blob/develop/contracts/metaTxToken/MetaTxToken.sol). -## Step 2 - Deploying the colony contract - -The most important step. Here the actualy colony contract will be deployed. This happens by executing a contract method on the `ColonyNetwork` (as opposed to a deploy-transaction): +You can also use an already existing token. For that, instead of passing in the token's details as the first argument, just use the token's address (it needs to be in the same chain your Colony is deployed in), like so: ```typescript -// Create actual colony (deploys Colony contract) +// Use USDC on Gnosis chain as the native token const [{ colonyAddress }] = await colonyNetwork - .createColony(tokenAddress, 'colonytestname') + .createColony('0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', 'anothertestname') .tx(); ``` -Here a label for the colony can be assigned. These are unique, so pick one that's not already taken. The `createColony` method will check that. Alternatively, the `colonyNetwork.getColonyAddress(label)` function can be used. +As the second argument a label for the Colony is assigned. These are unique, so pick one that's not already taken. The `createColony` method will check that. Alternatively, the `colonyNetwork.getColonyAddress(label)` function can be used. The label is used by the dApp as a short name and for looking up the Colony's address. **If the own token was used and no extension installation is desired we would be done here. This is not recommended though, as one will at least need the `OneTxPayment` extension to properly use Colony at this stage. -## Step 3 - Instantiate the Colony for the first time +## Step 2 - Instantiate the Colony for the first time Let's instantiate the colony (this is the code used to instantiate an existing colony) and the token: @@ -57,22 +52,18 @@ const colony = await colonyNetwork.getColony(colonyAddress); const { token } = colony; ``` -## Step 4 (optional) - Deploy the token authority and set the owner +## Step 3 - Set the Colony as owner of the token -The token authority is a contract that glues the token and the colony together and makes it possible for the colony to manage and move the token. The token authority can be deployed using the `deployAuthority` method on the `Token`. After that, another transaction is needed to set the token's `authority` to the one that was just deployed. If the token does not support the `setAuthority` method, this step should be skipped. +The token authority is a contract that glues the token and the colony together and makes it possible for the colony to manage and move the token. The first transaction is needed to set the token's `authority` to the one that was just deployed. After that we set the Colony to one of the token's "owners", so that it has permissions to access extra token functions (like `mint`). If your token was newly created in step 1 you will want to do this! If the token does not support the `setAuthority` method, this step should be skipped. ```typescript -// Deploy TokenAuthority -const [{ tokenAuthorityAddress }] = await token - .deployAuthority([colonyAddress]) - .tx(); -// Set the token's authority to the freshly deployed one +// Set the token's authority to the freshly deployed one (see step 1) await token.setAuthority(tokenAuthorityAddress).tx(); // Set the token's owner (the colony), to have permissions to execute authorized functions (like `mint`) await colony.token.setOwner(colony.address).tx(); ``` -## Step 5 - Install the `OneTxPayment` extension +## Step 4 - Install the `OneTxPayment` extension As mentioned earlier, this step is technically optional as well but if the colony is supposed to be used productively, a form of payment extension is needed. Currently only the `OneTxPayment` extension is supported. Install it like so: diff --git a/examples/node/create.ts b/examples/node/create.ts index e4e167ae..5c048bb3 100644 --- a/examples/node/create.ts +++ b/examples/node/create.ts @@ -10,41 +10,55 @@ import { const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); +if (!process.env.PRIVATE_KEY) { + throw new Error( + `Please provide a valid private key as an environment variable: PRIVATE_KEY`, + ); +} + // Deploy a Colony with everything that's needed const start = async () => { const signer = new Wallet(process.env.PRIVATE_KEY as string).connect( provider, ); const colonyNetwork = await ColonyNetwork.init(signer); - // Create token to be used with Colony - const [{ tokenAddress }] = await colonyNetwork - .deployToken('Test token', 'TOT') - .tx(); - console.info('Token address', tokenAddress); - if (!tokenAddress) { - return; - } - // Create actual colony (deploys Colony contract) - const [{ colonyAddress }] = await colonyNetwork - .createColony(tokenAddress, 'createcolonytest3') - .tx(); - if (!colonyAddress) { + + // ** 1st transaction ** + + // This command deploys a Colony and creates a Token and a TokenAuthority contact + // The TokenAuthority can be seen as a "permission-manager" for the token + const [{ colonyAddress, tokenAddress, tokenAuthorityAddress }] = + await colonyNetwork + .createColony( + // Define the token that will be deployed alongside the Colony (ERC20) + { name: 'Test token', symbol: 'TOT' }, + // Make this name unique. The name below is likely to be taken already + 'createcolonytest6', + ) + .tx(); + + if (!colonyAddress || !tokenAddress || !tokenAuthorityAddress) { return; } + + console.info('Token address', tokenAddress); console.info('Colony address', colonyAddress); - // Instantiate colony and token - const colony = await colonyNetwork.getColony(colonyAddress); - // Deploy TokenAuthority - const [{ tokenAuthorityAddress }] = await colony.deployTokenAuthority().tx(); - if (!tokenAuthorityAddress) { - return; - } console.info('Token authority address', tokenAuthorityAddress); + + // Instantiate colony + const colony = await colonyNetwork.getColony(colonyAddress); + + // ** 2nd and 3rd transaction ** + // Set the token's authority to the freshly deployed one if (colony.token instanceof ColonyToken) { + // TODO: MetaTxToken might support multicall in the future await colony.token.setAuthority(tokenAuthorityAddress).tx(); await colony.token.setOwner(colony.address).tx(); } + + // ** 4th transaction ** + // Install OneTxPayment extension const [{ extensionId, version }] = await colony .installExtension(SupportedExtension.oneTx) @@ -54,23 +68,25 @@ const start = async () => { } console.info('ExtensionId', extensionId); console.info('Extension version', version); - // Intantiate Colony again to update the extensions + + // Update colony to see the newly installed extension await colony.updateExtensions(); if (!colony.ext.oneTx) { console.error('Could not instantiate OneTx extension within Colony'); return; } + + // ** 5th transaction ** + // Give Administration and Funding roles to OneTxPayment extension - const [{ user, setTo, role }] = await colony + await colony .setRoles(colony.ext.oneTx.address, [ ColonyRole.Administration, ColonyRole.Funding, ]) .tx(); - if (!role) { - return; - } - console.info(user, setTo, ColonyRole[role]); + + console.info('Done :)'); }; start(); diff --git a/src/ColonyNetwork/ColonyNetwork.ts b/src/ColonyNetwork/ColonyNetwork.ts index 007609ad..0b9eef16 100644 --- a/src/ColonyNetwork/ColonyNetwork.ts +++ b/src/ColonyNetwork/ColonyNetwork.ts @@ -1,6 +1,7 @@ import type { ColonyAddedEventObject, ColonyMetadataEventObject, + TokenAuthorityDeployedEventObject, TokenDeployedEventObject, UserLabelRegisteredEventObject, } from '@colony/colony-js/events'; @@ -62,6 +63,15 @@ export interface ColonyNetworkConfig { metaTxBroadcasterEndpoint?: string; } +export interface TokenData { + /** The token's name (e.g. Colony Network Token) */ + name: string; + /** The token's symbol (e.g. CLNY) */ + symbol: string; + /** The token's decimals (defaults to 18) */ + decimals?: number; +} + export class ColonyNetwork { config: ColonyNetworkConfig; @@ -273,7 +283,8 @@ export class ColonyNetwork { * Creates a new colony with IPFS metadata. To edit metadata at a later point you can call the [[Colony.editColony]] method. * * @remarks - * There is more to creating a fully functional colony that can be used within the dapp than just calling this function. See the [Colony Creation Guide](../../guides/colony-creation.md). + * There is more to creating a fully functional colony that can be used within the dapp than just calling this function. + * See the [Colony Creation Guide](../../guides/colony-creation.md). * * @example * ```typescript @@ -285,17 +296,21 @@ export class ColonyNetwork { * // (forced transaction example) * // (also notice that this requires an upload-capable IPFS adapter) * await colonyNetwork.createColony( - * // Use USDC on Gnosis chain as the native token - * '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', { + * // Create a new token ('COOL') for this Colony + * { name: 'Cool token', symbol: 'COOL' }, + * 'coolony', + * { * colonyDisplayName: 'Cool Colony', * // IPFS hash to an image file * colonyAvatarHash: 'QmS26o1Cmsrx7iw1SSFGEcy22TVDq6VmEZ4XNjpWFyaKUe', * // List of token addresses that the Colony should be initialized with (can be changed later) - excluding ETH and the native token from above - * colonyTokens: [Tokens.CLNY], - * }).tx(); + * colonyTokens: [Tokens.Gnosis.CLNY], + * }).tx(); * })(); * ``` * + * @param token - Create a new ERC20-compatible token by passing in its name and symbol or use an existing token by passing in its contract address + * @param label - The Colony's label. This is going to be part of the URL to look up the Colony within the dApp * @param metadata - The team metadata you would like to add (or an IPFS CID pointing to valid metadata). If [[ColonyMetadata]] is provided directly (as opposed to a [CID](https://docs.ipfs.io/concepts/content-addressing/#identifier-formats) for a JSON file) this requires an [[IpfsAdapter]] that can upload and pin to IPFS (like the [[PinataAdapter]]). See its documentation for more information. * * @returns A transaction creator @@ -306,7 +321,9 @@ export class ColonyNetwork { * | :------ | :------ | :------ | * | `colonyId` | BigNumber | Auto-incremented integer id of the colony | * | `colonyAddress` | string | Address of the newly deployed colony contract | - * | `token` | string | Address of the token that is used as the colony's native token | + * | `tokenAddress` | string | Address of the token that is used as the colony's native token | + * | `tokenAuthorityAddress` | string | Address of the token authority (the token's permission manager) contract | + * | `token` | string | Alias of `token` * | `metadata` | string | IPFS CID of metadata attached to this transaction | * * #### Metadata @@ -320,13 +337,18 @@ export class ColonyNetwork { * | `colonyTokens` | string[] | A list of additional tokens that should be in the colony's "address book" | */ createColony( - tokenAddress: string, + token: string | TokenData, label: string, metadata: ColonyMetadata | string, ): MetaTxCreator< ColonyNetworkClient, - 'createColony(address,uint256,string,string)', - Expand, + 'createColonyForFrontend', + Expand< + TokenDeployedEventObject & + ColonyAddedEventObject & + TokenAuthorityDeployedEventObject & + ColonyMetadataEventObject + >, MetadataType.Colony >; @@ -336,7 +358,8 @@ export class ColonyNetwork { * Creates a new Colony without IPFS metadata. To add metadata at a later point you can call the [[Colony.editColony]] method. * * @remarks - * There is more to creating a fully functional colony that can be used within the dapp than just calling this function. See the [Colony Creation Guide](../../guides/colony-creation.md). + * There is more to creating a fully functional colony that can be used within the dapp than just calling this function. + * See the [Colony Creation Guide](../../guides/colony-creation.md). * * @example * ```typescript @@ -346,11 +369,14 @@ export class ColonyNetwork { * // (forced transaction example) * await colonyNetwork * // Use USDC on Gnosis chain as the native token - * .createColony('0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83') + * .createColony('0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', 'coolony') * .tx(); * })(); * ``` * + * @param token - Create a new ERC20-compatible token by passing in its name and symbol or use an existing token by passing in its contract address + * @param label - The Colony's label. This is going to be part of the URL to look up the Colony within the dApp + * * @returns A transaction creator * * #### Event data @@ -359,32 +385,73 @@ export class ColonyNetwork { * | :------ | :------ | :------ | * | `colonyId` | BigNumber | Auto-incremented integer id of the colony | * | `colonyAddress` | string | Address of the newly deployed colony contract | - * | `token` | string | Address of the token that is used as the colony's native token | + * | `tokenAddress` | string | Address of the token that is used as the colony's native token | + * | `tokenAuthorityAddress` | string | Address of the token authority (the token's permission manager) contract | + * | `token` | string | Alias of `token` */ createColony( - tokenAddress: string, + token: string | TokenData, label: string, ): MetaTxCreator< ColonyNetworkClient, - 'createColony(address,uint256,string)', - Expand, + 'createColonyForFrontend', + Expand< + TokenDeployedEventObject & + ColonyAddedEventObject & + TokenAuthorityDeployedEventObject & { metadata?: undefined } + >, MetadataType >; createColony( - tokenAddress: string, + token: string | TokenData, label: string, metadata?: ColonyMetadata | string, ) { - const checkLabel = async () => { + const prepareArgs = async () => { const existingAddress = await this.getColonyAddress(label); if (existingAddress) { throw new Error(`Colony with label ${label} already exists`); } - return [tokenAddress, Colony.getLatestSupportedVersion(), label] as [ + + // TODO: check all arguments for existance + + if (typeof token != 'string') { + if (!token.name) { + throw new Error('Token name is required'); + } + if (!token.symbol) { + throw new Error('Token symbol is required'); + } + // TODO: check decimals type + + return [ + AddressZero, + token.name, + token.symbol, + token.decimals || 18, + 0, + label, + '', + ] as [ + string, + string, + string, + BigNumberish, + BigNumberish, + string, + string, + ]; + } + + return [token, '', '', 0, 0, label, ''] as [ + string, + string, string, BigNumberish, + BigNumberish, + string, string, ]; }; @@ -392,35 +459,41 @@ export class ColonyNetwork { if (!metadata) { return this.createMetaTxCreator( this.networkClient, - 'createColony(address,uint256,string)', - checkLabel, + 'createColonyForFrontend', + prepareArgs, async (receipt) => ({ + ...extractEvent('TokenDeployed', receipt), ...extractEvent('ColonyAdded', receipt), + ...extractEvent( + 'TokenAuthorityDeployed', + receipt, + ), }), ); } return this.createMetaTxCreator( this.networkClient, - 'createColony(address,uint256,string,string)', + 'createColonyForFrontend', async () => { - await checkLabel(); - - let cid: string; + const args = await prepareArgs(); if (typeof metadata == 'string') { - cid = metadata; + args[6] = metadata; } else { - cid = await this.ipfs.uploadMetadata(MetadataType.Colony, metadata); + args[6] = await this.ipfs.uploadMetadata( + MetadataType.Colony, + metadata, + ); } - return [ - tokenAddress, - Colony.getLatestSupportedVersion(), - label, - cid, - ] as [string, BigNumberish, string, string]; + return args; }, async (receipt) => ({ + ...extractEvent('TokenDeployed', receipt), ...extractEvent('ColonyAdded', receipt), + ...extractEvent( + 'TokenAuthorityDeployed', + receipt, + ), }), { metadataType: MetadataType.Colony, @@ -559,11 +632,11 @@ export class ColonyNetwork { } /** - * Deploy a "special" colony ERC20 token + * Deploy a "special" Colony ERC20 token * * If there is not token yet that should be used with the Colony, this is the canonical way to create one. * - * This is a supercharged ERC20 token contract, that not only has a permissioned `mint` function (that can be used from the colony) but also supports Metatransactions. In order to fully use its permissioned system with a colony, some extra steps have to be taken. See the [Colony Creation Guide](../../guides/colony-creation.md). + * This is a supercharged ERC20 token contract, that not only has a permissioned `mint` function (that can be used from the colony) but also supports Metatransactions. In order to fully use its permissioned system with a Colony, some extra steps have to be taken. See the [Colony Creation Guide](../../guides/colony-creation.md). * * @remarks * The token deployed with this function is locked by default. Call `unlockToken()` on the Colony at a later point to unlock it. diff --git a/src/ColonyNetwork/ColonyToken.ts b/src/ColonyNetwork/ColonyToken.ts index 4bee0cc7..e3570623 100644 --- a/src/ColonyNetwork/ColonyToken.ts +++ b/src/ColonyNetwork/ColonyToken.ts @@ -71,7 +71,7 @@ export class ColonyToken extends ERC20Token { * })(); * ``` * - * @param tokenAuthorityAddress - Address of the TokenAuthority contract + * @param address - Address of the TokenAuthority contract * * @returns A transaction creator * @@ -81,11 +81,11 @@ export class ColonyToken extends ERC20Token { * | :------ | :------ | :------ | * | `authority` | string | The address of the tokenAuthority that has been set | */ - setAuthority(tokenAuthorityAddress: string) { + setAuthority(address: string) { return this.colonyNetwork.createMetaTxCreator( this.tokenClient, 'setAuthority', - [tokenAuthorityAddress], + [address], async (receipt) => ({ ...extractEvent('LogSetAuthority', receipt), }),