├── .nvmrc ├── .husky ├── .gitignore └── pre-commit ├── examples ├── browser │ ├── .gitignore │ ├── web │ │ ├── metamask.html │ │ ├── events.html │ │ ├── basic.html │ │ ├── advanced.html │ │ ├── local-basic.html │ │ ├── index.html │ │ ├── local-reputation.html │ │ ├── motions.html │ │ └── local-motions.html │ └── src │ │ ├── index.ts │ │ ├── metamask.ts │ │ ├── basic.ts │ │ ├── local-basic.ts │ │ ├── events.ts │ │ └── local-reputation.ts ├── node │ ├── graph-colony.ts │ ├── metatx.ts │ ├── basic.ts │ ├── index.ts │ ├── graph.ts │ ├── graph-subscription.ts │ ├── roles.ts │ ├── automation.ts │ └── create.ts └── helpers.ts ├── .gitignore ├── .release-it.json ├── docs ├── api │ ├── interfaces │ │ ├── AnnotationMetadata.md │ │ ├── TxConfig.md │ │ ├── NetworkClientOptions.md │ │ ├── DecisionMetadata.md │ │ ├── SupportedExtensions.md │ │ ├── ColonyEventManagerOptions.md │ │ ├── PermissionConfig.md │ │ ├── SubgraphClientOptions.md │ │ ├── ColonyTopic.md │ │ ├── DomainMetadata.md │ │ ├── BaseContract.md │ │ ├── EventSources.md │ │ ├── ColonyMultiFilter.md │ │ ├── Ethers6FilterByBlockHash.md │ │ ├── MetaTxBaseContract.md │ │ ├── ColonyMetadata.md │ │ ├── Ethers6Filter.md │ │ ├── ColonyNetworkOptions.md │ │ ├── GraphDomain.md │ │ ├── TxCreatorConfig.md │ │ ├── ColonyFilter.md │ │ ├── IpfsAdapter.md │ │ ├── ParsedLogTransactionReceipt.md │ │ └── ColonyEvent.md │ ├── enums │ │ ├── Vote.md │ │ ├── Tokens.Mainnet.md │ │ ├── SupportedExtension.md │ │ ├── Tokens.Gnosis.md │ │ ├── MetadataType.md │ │ ├── Id.md │ │ ├── UserLabelSuffix.md │ │ ├── ColonyLabelSuffix.md │ │ ├── MotionState.md │ │ ├── Network.md │ │ ├── ColonyRole.md │ │ ├── ColonyRpcEndpoint.md │ │ ├── MetaTxBroadCasterEndpoint.md │ │ ├── TeamColor.md │ │ └── Extension.md │ ├── modules │ │ └── Tokens.md │ └── classes │ │ ├── ColonyGraph.md │ │ ├── CloudflareReadonlyAdapter.md │ │ ├── PinataAdapter.md │ │ ├── TxCreator.md │ │ ├── OneTxPayment.md │ │ ├── MetaTxCreator.md │ │ └── ERC20Token.md ├── guides │ ├── index.md │ ├── metadata.md │ ├── transactions.md │ └── colony-creation.md ├── index.md └── getting-started │ ├── index.md │ └── your-first-transaction.md ├── .lintstagedrc.js ├── src ├── events │ └── index.ts ├── TxCreator │ ├── index.ts │ ├── MetaTxCreator.ts │ ├── EIP2612TxCreator.ts │ └── TxCreator.ts ├── ipfs │ ├── types.ts │ ├── index.ts │ ├── IpfsAdapter.ts │ ├── CloudflareReadonlyAdapter.ts │ ├── PinataAdapter.ts │ └── IpfsMetadata.ts ├── ColonyNetwork │ ├── index.ts │ ├── ERC2612Token.ts │ ├── ColonyToken.ts │ ├── TokenLocking.ts │ └── ERC20Token.ts ├── index.ts ├── types.ts ├── graph │ ├── ColonyGraph.ts │ └── index.ts ├── constants.ts └── utils.ts ├── tsconfig.build.json ├── .editorconfig ├── scripts └── no-git-changes.sh ├── typedoc.json ├── tsconfig.json ├── .github └── workflows │ ├── examples.yml │ └── build.yml ├── CONTRIBUTING.md ├── .eslintrc ├── package.json ├── CODE_OF_CONDUCT.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.18.1 2 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /examples/browser/.gitignore: -------------------------------------------------------------------------------- 1 | web/*.js 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist 3 | .eslintcache 4 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "release": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /docs/api/interfaces/AnnotationMetadata.md: -------------------------------------------------------------------------------- 1 | # Interface: AnnotationMetadata 2 | 3 | ## Properties 4 | 5 | ### annotationMsg 6 | 7 | • **annotationMsg**: `string` 8 | -------------------------------------------------------------------------------- /docs/api/enums/Vote.md: -------------------------------------------------------------------------------- 1 | # Enumeration: Vote 2 | 3 | ## Enumeration Members 4 | 5 | ### Nay 6 | 7 | • **Nay** = ``0`` 8 | 9 | ___ 10 | 11 | ### Yay 12 | 13 | • **Yay** = ``1`` 14 | -------------------------------------------------------------------------------- /docs/guides/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Helpful guides 3 | 4 | sidebar_position: 2 5 | --- 6 | 7 | import DocCardList from '@theme/DocCardList'; 8 | 9 | # Guides 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/api/modules/Tokens.md: -------------------------------------------------------------------------------- 1 | # Namespace: Tokens 2 | 3 | Shortcut to common token addresses 4 | 5 | ## Enumerations 6 | 7 | - [Gnosis](../enums/Tokens.Gnosis.md) 8 | - [Mainnet](../enums/Tokens.Mainnet.md) 9 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '**/*.ts?(x)': (filenames) => [ 3 | `eslint --cache --fix ${filenames.join(' ')}`, 4 | 'tsc -p tsconfig.json --noEmit', 5 | 'typedoc', 6 | 'git add docs/api', 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /docs/api/interfaces/TxConfig.md: -------------------------------------------------------------------------------- 1 | # Interface: TxConfig 2 | 3 | ## Type parameters 4 | 5 | | Name | 6 | | :------ | 7 | | `M` | 8 | 9 | ## Properties 10 | 11 | ### metadataType 12 | 13 | • `Optional` **metadataType**: `M` 14 | -------------------------------------------------------------------------------- /src/events/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ColonyEvent, 3 | ColonyEventManager, 4 | ColonyEventManagerOptions, 5 | ColonyTopic, 6 | ColonyFilter, 7 | ColonyMultiFilter, 8 | EventSource, 9 | EventSources, 10 | } from './ColonyEventManager'; 11 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "**/__tests__"], 4 | "include": ["src/**/*"], 5 | "compilerOptions": { 6 | "declaration": false, 7 | "noEmit": false, 8 | "rootDir": "./src" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/TxCreator/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | BaseContract, 3 | TxCreatorConfig, 4 | TxConfig, 5 | TxCreator, 6 | } from './TxCreator'; 7 | export { MetaTxBaseContract, MetaTxCreator } from './MetaTxCreator'; 8 | export { PermissionConfig, ColonyTxCreator } from './ColonyTxCreator'; 9 | -------------------------------------------------------------------------------- /src/ipfs/types.ts: -------------------------------------------------------------------------------- 1 | export enum DomainColor { 2 | LightPink, 3 | Pink, 4 | Black, 5 | EmeraldGreen, 6 | Blue, 7 | Yellow, 8 | Red, 9 | Green, 10 | Periwinkle, 11 | Gold, 12 | Aqua, 13 | BlueGrey, 14 | Purple, 15 | Orange, 16 | Magenta, 17 | PurpleGrey, 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | end_of_line = lf 8 | 9 | [*.{js,json,ts,tsx}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.{md,markdown}] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /docs/api/interfaces/NetworkClientOptions.md: -------------------------------------------------------------------------------- 1 | # Interface: NetworkClientOptions 2 | 3 | ## Properties 4 | 5 | ### networkAddress 6 | 7 | • `Optional` **networkAddress**: `string` 8 | 9 | ___ 10 | 11 | ### reputationOracleEndpoint 12 | 13 | • `Optional` **reputationOracleEndpoint**: `string` 14 | -------------------------------------------------------------------------------- /docs/api/interfaces/DecisionMetadata.md: -------------------------------------------------------------------------------- 1 | # Interface: DecisionMetadata 2 | 3 | ## Properties 4 | 5 | ### description 6 | 7 | • **description**: `string` 8 | 9 | ___ 10 | 11 | ### motionDomainId 12 | 13 | • **motionDomainId**: `number` 14 | 15 | ___ 16 | 17 | ### title 18 | 19 | • **title**: `string` 20 | -------------------------------------------------------------------------------- /src/ipfs/index.ts: -------------------------------------------------------------------------------- 1 | export { IpfsMetadata, MetadataEvent, MetadataValue } from './IpfsMetadata'; 2 | 3 | export { default as PinataAdapter } from './PinataAdapter'; 4 | export { default as CloudflareReadonlyAdapter } from './CloudflareReadonlyAdapter'; 5 | 6 | export type { default as IpfsAdapter } from './IpfsAdapter'; 7 | -------------------------------------------------------------------------------- /docs/api/interfaces/SupportedExtensions.md: -------------------------------------------------------------------------------- 1 | # Interface: SupportedExtensions 2 | 3 | ## Properties 4 | 5 | ### motions 6 | 7 | • `Optional` **motions**: [`VotingReputation`](../classes/VotingReputation.md) 8 | 9 | ___ 10 | 11 | ### oneTx 12 | 13 | • `Optional` **oneTx**: [`OneTxPayment`](../classes/OneTxPayment.md) 14 | -------------------------------------------------------------------------------- /scripts/no-git-changes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | 5 | if [[ `git status --porcelain` ]]; then 6 | echo "GIT changes detected! Please always run all pre-commit hooks. `npm run build-docs` should help in most cases" 7 | 8 | git status --verbose --verbose 9 | 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyEventManagerOptions.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyEventManagerOptions 2 | 3 | Additional options for the [ColonyEventManager](../classes/ColonyEventManager.md) 4 | 5 | ## Properties 6 | 7 | ### ipfsAdapter 8 | 9 | • `Optional` **ipfsAdapter**: [`IpfsAdapter`](IpfsAdapter.md) 10 | 11 | Provide a custom [IpfsAdapter](IpfsAdapter.md) 12 | -------------------------------------------------------------------------------- /docs/api/interfaces/PermissionConfig.md: -------------------------------------------------------------------------------- 1 | # Interface: PermissionConfig 2 | 3 | ## Properties 4 | 5 | ### address 6 | 7 | • `Optional` **address**: `string` 8 | 9 | ___ 10 | 11 | ### domain 12 | 13 | • **domain**: `BigNumberish` 14 | 15 | ___ 16 | 17 | ### roles 18 | 19 | • **roles**: [`ColonyRole`](../enums/ColonyRole.md) \| [`ColonyRole`](../enums/ColonyRole.md)[] 20 | -------------------------------------------------------------------------------- /docs/api/interfaces/SubgraphClientOptions.md: -------------------------------------------------------------------------------- 1 | # Interface: SubgraphClientOptions 2 | 3 | ## Properties 4 | 5 | ### endpointHttp 6 | 7 | • `Optional` **endpointHttp**: `string` 8 | 9 | The GraphQL endpoint for HTTP connections 10 | 11 | ___ 12 | 13 | ### endpointWs 14 | 15 | • `Optional` **endpointWs**: `string` 16 | 17 | The GraphQL endpoint for Websocket connections 18 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyTopic.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyTopic 2 | 3 | A Colony specific topic that keeps track of which contract it belongs to 4 | 5 | ## Properties 6 | 7 | ### eventName 8 | 9 | • **eventName**: `string` 10 | 11 | ___ 12 | 13 | ### eventSource 14 | 15 | • **eventSource**: keyof [`EventSources`](EventSources.md) 16 | 17 | ___ 18 | 19 | ### topic 20 | 21 | • **topic**: `string` 22 | -------------------------------------------------------------------------------- /docs/api/enums/Tokens.Mainnet.md: -------------------------------------------------------------------------------- 1 | # Enumeration: Mainnet 2 | 3 | [Tokens](../modules/Tokens.md).Mainnet 4 | 5 | Tokens deployed on Mainnet 6 | 7 | ## Enumeration Members 8 | 9 | ### ETH 10 | 11 | • **ETH** = ``"0x0000000000000000000000000000000000000000"`` 12 | 13 | ETH on Mainnet 14 | 15 | ___ 16 | 17 | ### Mainnet 18 | 19 | • **Mainnet** = ``"0x3E828ac5C480069D4765654Fb4b8733b910b13b2"`` 20 | 21 | CLNY on Mainnet 22 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "API", 3 | "entryPoints": ["./src/index.ts"], 4 | "tsconfig": "./tsconfig.json", 5 | "out": "docs/api", 6 | "excludeInternal": true, 7 | "excludePrivate": true, 8 | "excludeProtected": true, 9 | "githubPages": false, 10 | "disableSources": true, 11 | "readme": "none", 12 | "plugin": "typedoc-plugin-markdown", 13 | "hideBreadcrumbs": true, 14 | "hideInPageTOC": true 15 | } 16 | -------------------------------------------------------------------------------- /docs/api/enums/SupportedExtension.md: -------------------------------------------------------------------------------- 1 | # Enumeration: SupportedExtension 2 | 3 | Extensions that are supported by Colony SDK 4 | 5 | ## Enumeration Members 6 | 7 | ### motion 8 | 9 | • **motion** = ``"motion"`` 10 | 11 | Motions & Disputes (VotingReputation) 12 | 13 | ___ 14 | 15 | ### oneTx 16 | 17 | • **oneTx** = ``"oneTx"`` 18 | 19 | One Transaction Payment (OneTxPayment - enabled by default in Dapp created colonies) 20 | -------------------------------------------------------------------------------- /src/ColonyNetwork/index.ts: -------------------------------------------------------------------------------- 1 | export { ColonyNetwork, ColonyNetworkOptions } from './ColonyNetwork'; 2 | export { Colony, SupportedExtension, SupportedExtensions } from './Colony'; 3 | export { ColonyToken } from './ColonyToken'; 4 | export { ERC20Token } from './ERC20Token'; 5 | export { ERC2612Token } from './ERC2612Token'; 6 | export { OneTxPayment } from './OneTxPayment'; 7 | export { VotingReputation, Vote } from './VotingReputation'; 8 | -------------------------------------------------------------------------------- /docs/api/enums/Tokens.Gnosis.md: -------------------------------------------------------------------------------- 1 | # Enumeration: Gnosis 2 | 3 | [Tokens](../modules/Tokens.md).Gnosis 4 | 5 | Tokens deployed on Gnosis Chain 6 | 7 | ## Enumeration Members 8 | 9 | ### CLNY 10 | 11 | • **CLNY** = ``"0xc9B6218AffE8Aba68a13899Cbf7cF7f14DDd304C"`` 12 | 13 | CLNY on Gnosis Chain 14 | 15 | ___ 16 | 17 | ### XDAI 18 | 19 | • **XDAI** = ``"0x0000000000000000000000000000000000000000"`` 20 | 21 | XDAI on Gnosis Chain 22 | -------------------------------------------------------------------------------- /docs/api/interfaces/DomainMetadata.md: -------------------------------------------------------------------------------- 1 | # Interface: DomainMetadata 2 | 3 | ## Hierarchy 4 | 5 | - **`DomainMetadata`** 6 | 7 | ↳ [`GraphDomain`](GraphDomain.md) 8 | 9 | ## Properties 10 | 11 | ### domainColor 12 | 13 | • `Optional` **domainColor**: `number` 14 | 15 | ___ 16 | 17 | ### domainName 18 | 19 | • `Optional` **domainName**: `string` 20 | 21 | ___ 22 | 23 | ### domainPurpose 24 | 25 | • `Optional` **domainPurpose**: `string` 26 | -------------------------------------------------------------------------------- /docs/api/enums/MetadataType.md: -------------------------------------------------------------------------------- 1 | # Enumeration: MetadataType 2 | 3 | ## Enumeration Members 4 | 5 | ### Annotation 6 | 7 | • **Annotation** = ``"annotation"`` 8 | 9 | ___ 10 | 11 | ### Colony 12 | 13 | • **Colony** = ``"colony"`` 14 | 15 | ___ 16 | 17 | ### Decision 18 | 19 | • **Decision** = ``"decision"`` 20 | 21 | ___ 22 | 23 | ### Domain 24 | 25 | • **Domain** = ``"domain"`` 26 | 27 | ___ 28 | 29 | ### Misc 30 | 31 | • **Misc** = ``"misc"`` 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2018", "dom"], 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "strict": true, 11 | "target": "es2015", 12 | "types": ["node"], 13 | "rootDir": "./" 14 | }, 15 | "include": ["src/**/*", "examples/**/*"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /docs/api/enums/Id.md: -------------------------------------------------------------------------------- 1 | # Enumeration: Id 2 | 3 | Shortcuts to certain IDs within Colony 4 | 5 | ## Enumeration Members 6 | 7 | ### RootDomain 8 | 9 | • **RootDomain** = ``1`` 10 | 11 | The id of the root-domain in all colonies 12 | 13 | ___ 14 | 15 | ### RootPot 16 | 17 | • **RootPot** = ``1`` 18 | 19 | The id of the root fundig pot in all colonies 20 | 21 | ___ 22 | 23 | ### SkillIgnore 24 | 25 | • **SkillIgnore** = ``0`` 26 | 27 | Ignore the skill id for this method (global skill 0) 28 | -------------------------------------------------------------------------------- /docs/api/interfaces/BaseContract.md: -------------------------------------------------------------------------------- 1 | # Interface: BaseContract 2 | 3 | ## Hierarchy 4 | 5 | - **`BaseContract`** 6 | 7 | ↳ [`MetaTxBaseContract`](MetaTxBaseContract.md) 8 | 9 | ## Properties 10 | 11 | ### address 12 | 13 | • **address**: `string` 14 | 15 | ___ 16 | 17 | ### functions 18 | 19 | • **functions**: `Object` 20 | 21 | #### Index signature 22 | 23 | ▪ [key: `string`]: (...`args`: `any`[]) => `Promise`<`any`\> 24 | 25 | ___ 26 | 27 | ### interface 28 | 29 | • **interface**: `Interface` 30 | -------------------------------------------------------------------------------- /docs/api/interfaces/EventSources.md: -------------------------------------------------------------------------------- 1 | # Interface: EventSources 2 | 3 | Valid sources for Colony emitted events. Used to map the parsed event data 4 | 5 | ## Properties 6 | 7 | ### Colony 8 | 9 | • **Colony**: `IColonyEvents` 10 | 11 | ___ 12 | 13 | ### ColonyNetwork 14 | 15 | • **ColonyNetwork**: `IColonyNetworkEvents` 16 | 17 | ___ 18 | 19 | ### OneTxPayment 20 | 21 | • **OneTxPayment**: `OneTxPaymentEvents` 22 | 23 | ___ 24 | 25 | ### VotingReputation 26 | 27 | • **VotingReputation**: `VotingReputationEvents` 28 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyMultiFilter.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyMultiFilter 2 | 3 | ColonyFilter with support for multi-events 4 | For the multi-event compatible filters the following assumptions prevail: 5 | - `address` is a mandatory field 6 | - The list of filter topics is always OR'd, never AND'd. 7 | - `fromBlock` and `toBlock` are not available 8 | 9 | ## Properties 10 | 11 | ### address 12 | 13 | • **address**: `string` 14 | 15 | ___ 16 | 17 | ### colonyTopics 18 | 19 | • **colonyTopics**: [`ColonyTopic`](ColonyTopic.md)[] 20 | -------------------------------------------------------------------------------- /examples/node/graph-colony.ts: -------------------------------------------------------------------------------- 1 | import { providers } from 'ethers'; 2 | 3 | import { ColonyNetwork, ColonyRpcEndpoint } from '../../src'; 4 | 5 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 6 | 7 | // Get the Metacolony's domains including their metadata 8 | const start = async () => { 9 | const colonyNetwork = await ColonyNetwork.init(provider); 10 | const colony = await colonyNetwork.getColony( 11 | '0x364B3153A24bb9ECa28B8c7aCeB15E3942eb4fc5', 12 | ); 13 | const data = await colony.graph.getTeamsWithMetadata(); 14 | console.info(data); 15 | }; 16 | 17 | start(); 18 | -------------------------------------------------------------------------------- /docs/api/enums/UserLabelSuffix.md: -------------------------------------------------------------------------------- 1 | # Enumeration: UserLabelSuffix 2 | 3 | ## Enumeration Members 4 | 5 | ### Custom 6 | 7 | • **Custom** = ``".user.joincolony.test"`` 8 | 9 | ___ 10 | 11 | ### Gnosis 12 | 13 | • **Gnosis** = ``".user.joincolony.colonyxdai"`` 14 | 15 | ___ 16 | 17 | ### Goerli 18 | 19 | • **Goerli** = ``".user.joincolony.test"`` 20 | 21 | ___ 22 | 23 | ### Mainnet 24 | 25 | • **Mainnet** = ``".user.joincolony.eth"`` 26 | 27 | ___ 28 | 29 | ### Xdai 30 | 31 | • **Xdai** = ``".user.joincolony.colonyxdai"`` 32 | 33 | ___ 34 | 35 | ### XdaiQa 36 | 37 | • **XdaiQa** = ``".user.joincolony.colonyxdai"`` 38 | -------------------------------------------------------------------------------- /examples/node/metatx.ts: -------------------------------------------------------------------------------- 1 | import { constants, providers, Wallet } from 'ethers'; 2 | 3 | import { ColonyNetwork, ColonyRpcEndpoint } from '../../src'; 4 | 5 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 6 | 7 | // Claim ETH for the MetaColony using a Metatransaction 8 | const start = async () => { 9 | const signer = Wallet.createRandom().connect(provider); 10 | const colonyNetwork = await ColonyNetwork.init(signer); 11 | const colony = await colonyNetwork.getMetaColony(); 12 | const res = await colony.claimFunds(constants.AddressZero).tx(); 13 | console.info(res); 14 | }; 15 | 16 | start(); 17 | -------------------------------------------------------------------------------- /examples/browser/web/metamask.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - connecting to MetaMask

11 |

Click the button to connect to MetaMask

12 | 13 |
14 |
15 | Message 16 |
17 |

18 |

19 |
20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { MotionState, NetworkClientOptions, Tokens } from '@colony/colony-js'; 2 | export type { 3 | AnnotationMetadata, 4 | ColonyMetadata, 5 | DecisionMetadata, 6 | DomainMetadata, 7 | MetadataType, 8 | } from '@colony/colony-event-metadata-parser'; 9 | 10 | export * from './ColonyNetwork'; 11 | export * from './TxCreator'; 12 | export * from './events'; 13 | export * from './graph'; 14 | export * from './ipfs'; 15 | export * from './constants'; 16 | export * from './utils'; 17 | 18 | export type { 19 | Ethers6Filter, 20 | Ethers6FilterByBlockHash, 21 | ParsedLogTransactionReceipt, 22 | } from './types'; 23 | -------------------------------------------------------------------------------- /docs/api/enums/ColonyLabelSuffix.md: -------------------------------------------------------------------------------- 1 | # Enumeration: ColonyLabelSuffix 2 | 3 | ## Enumeration Members 4 | 5 | ### Custom 6 | 7 | • **Custom** = ``".colony.joincolony.test"`` 8 | 9 | ___ 10 | 11 | ### Gnosis 12 | 13 | • **Gnosis** = ``".colony.joincolony.colonyxdai"`` 14 | 15 | ___ 16 | 17 | ### Goerli 18 | 19 | • **Goerli** = ``".colony.joincolony.test"`` 20 | 21 | ___ 22 | 23 | ### Mainnet 24 | 25 | • **Mainnet** = ``".colony.joincolony.eth"`` 26 | 27 | ___ 28 | 29 | ### Xdai 30 | 31 | • **Xdai** = ``".colony.joincolony.colonyxdai"`` 32 | 33 | ___ 34 | 35 | ### XdaiQa 36 | 37 | • **XdaiQa** = ``".colony.joincolony.colonyxdai"`` 38 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | name: Build examples and deploy 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@v2 13 | 14 | - name: Install and Build 🔧 15 | run: | 16 | npm ci 17 | npm run build-examples 18 | 19 | - name: Deploy 🚀 20 | uses: JamesIves/github-pages-deploy-action@releases/v4 21 | with: 22 | branch: gh-pages # The branch the action should deploy to. 23 | clean: true 24 | folder: examples/browser/web # The folder the action should deploy. 25 | -------------------------------------------------------------------------------- /docs/api/enums/MotionState.md: -------------------------------------------------------------------------------- 1 | # Enumeration: MotionState 2 | 3 | These are the various states a Motion might find itself in 4 | 5 | ## Enumeration Members 6 | 7 | ### Closed 8 | 9 | • **Closed** = ``4`` 10 | 11 | ___ 12 | 13 | ### Failed 14 | 15 | • **Failed** = ``7`` 16 | 17 | ___ 18 | 19 | ### Finalizable 20 | 21 | • **Finalizable** = ``5`` 22 | 23 | ___ 24 | 25 | ### Finalized 26 | 27 | • **Finalized** = ``6`` 28 | 29 | ___ 30 | 31 | ### Null 32 | 33 | • **Null** = ``0`` 34 | 35 | ___ 36 | 37 | ### Reveal 38 | 39 | • **Reveal** = ``3`` 40 | 41 | ___ 42 | 43 | ### Staking 44 | 45 | • **Staking** = ``1`` 46 | 47 | ___ 48 | 49 | ### Submit 50 | 51 | • **Submit** = ``2`` 52 | -------------------------------------------------------------------------------- /docs/api/interfaces/Ethers6FilterByBlockHash.md: -------------------------------------------------------------------------------- 1 | # Interface: Ethers6FilterByBlockHash 2 | 3 | Ethers 6 supports mulitple addresses in a filter. Until then we have this 4 | 5 | ## Hierarchy 6 | 7 | - `Omit`<`FilterByBlockHash`, ``"address"``\> 8 | 9 | ↳ **`Ethers6FilterByBlockHash`** 10 | 11 | ## Properties 12 | 13 | ### address 14 | 15 | • `Optional` **address**: `string` \| `string`[] 16 | 17 | ___ 18 | 19 | ### blockHash 20 | 21 | • `Optional` **blockHash**: `string` 22 | 23 | #### Inherited from 24 | 25 | Omit.blockHash 26 | 27 | ___ 28 | 29 | ### topics 30 | 31 | • `Optional` **topics**: (``null`` \| `string` \| `string`[])[] 32 | 33 | #### Inherited from 34 | 35 | Omit.topics 36 | -------------------------------------------------------------------------------- /examples/node/basic.ts: -------------------------------------------------------------------------------- 1 | import { providers } from 'ethers'; 2 | 3 | import { ColonyNetwork, ColonyRpcEndpoint, toEth } from '../../src'; 4 | 5 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 6 | 7 | // Get the Colony's CLNY funding in the ROOT team (id 1) 8 | const start = async () => { 9 | const colonyNetwork = await ColonyNetwork.init(provider); 10 | const metaColony = await colonyNetwork.getMetaColony(); 11 | const funding = await metaColony.getBalance(); 12 | const { address } = metaColony; 13 | console.info( 14 | `${toEth( 15 | funding, 16 | )} CLNY in root team of MetaColony with address: ${address}`, 17 | ); 18 | }; 19 | 20 | start(); 21 | -------------------------------------------------------------------------------- /examples/browser/web/events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - events

11 |

Listen to Colony Events as they unfold...

12 | 13 | 14 | 15 |
16 |
17 | Message 18 |
19 |

20 |

21 |
22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/browser/web/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - basic

11 |

Let's check the CLNY funding for a Colony

12 | 15 | 16 |
17 |
18 | Message 19 |
20 |

21 |

22 |
23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/api/interfaces/MetaTxBaseContract.md: -------------------------------------------------------------------------------- 1 | # Interface: MetaTxBaseContract 2 | 3 | ## Hierarchy 4 | 5 | - [`BaseContract`](BaseContract.md) 6 | 7 | ↳ **`MetaTxBaseContract`** 8 | 9 | ## Properties 10 | 11 | ### address 12 | 13 | • **address**: `string` 14 | 15 | #### Inherited from 16 | 17 | [BaseContract](BaseContract.md).[address](BaseContract.md#address) 18 | 19 | ___ 20 | 21 | ### functions 22 | 23 | • **functions**: `Functions` 24 | 25 | #### Overrides 26 | 27 | [BaseContract](BaseContract.md).[functions](BaseContract.md#functions) 28 | 29 | ___ 30 | 31 | ### interface 32 | 33 | • **interface**: `Interface` 34 | 35 | #### Overrides 36 | 37 | [BaseContract](BaseContract.md).[interface](BaseContract.md#interface) 38 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyMetadata.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyMetadata 2 | 3 | ## Properties 4 | 5 | ### colonyAvatarHash 6 | 7 | • `Optional` **colonyAvatarHash**: ``null`` \| `string` 8 | 9 | ___ 10 | 11 | ### colonyDisplayName 12 | 13 | • `Optional` **colonyDisplayName**: `string` 14 | 15 | ___ 16 | 17 | ### colonyName 18 | 19 | • `Optional` **colonyName**: `string` 20 | 21 | ___ 22 | 23 | ### colonySafes 24 | 25 | • `Optional` **colonySafes**: `SafeMetadata`[] 26 | 27 | ___ 28 | 29 | ### colonyTokens 30 | 31 | • `Optional` **colonyTokens**: `string`[] 32 | 33 | ___ 34 | 35 | ### isWhitelistActivated 36 | 37 | • `Optional` **isWhitelistActivated**: `boolean` 38 | 39 | ___ 40 | 41 | ### verifiedAddresses 42 | 43 | • `Optional` **verifiedAddresses**: `string`[] 44 | -------------------------------------------------------------------------------- /docs/api/enums/Network.md: -------------------------------------------------------------------------------- 1 | # Enumeration: Network 2 | 3 | Supported Ethereum networks. Use `Custom` if you'd like to bring your own deployment (e.g. local) 4 | 5 | ## Enumeration Members 6 | 7 | ### Custom 8 | 9 | • **Custom** = ``"Custom"`` 10 | 11 | Use this to specify an own main ColonyNetwork address in the options 12 | 13 | ___ 14 | 15 | ### Gnosis 16 | 17 | • **Gnosis** = ``"Xdai"`` 18 | 19 | Gnosis chain 20 | 21 | ___ 22 | 23 | ### Goerli 24 | 25 | • **Goerli** = ``"Goerli"`` 26 | 27 | Goerli testnet 28 | 29 | ___ 30 | 31 | ### Mainnet 32 | 33 | • **Mainnet** = ``"Mainnet"`` 34 | 35 | Ethereum Mainnet 36 | 37 | ___ 38 | 39 | ### Xdai 40 | 41 | • **Xdai** = ``"Xdai"`` 42 | 43 | Gnosis chain (alias) 44 | 45 | ___ 46 | 47 | ### XdaiQa 48 | 49 | • **XdaiQa** = ``"XdaiQa"`` 50 | 51 | Gnosis chain custom fork 52 | -------------------------------------------------------------------------------- /docs/api/interfaces/Ethers6Filter.md: -------------------------------------------------------------------------------- 1 | # Interface: Ethers6Filter 2 | 3 | Ethers 6 supports mulitple addresses in a filter. Until then we have this 4 | 5 | ## Hierarchy 6 | 7 | - `Omit`<`Filter`, ``"address"``\> 8 | 9 | ↳ **`Ethers6Filter`** 10 | 11 | ↳↳ [`ColonyFilter`](ColonyFilter.md) 12 | 13 | ## Properties 14 | 15 | ### address 16 | 17 | • `Optional` **address**: `string` \| `string`[] 18 | 19 | ___ 20 | 21 | ### fromBlock 22 | 23 | • `Optional` **fromBlock**: `BlockTag` 24 | 25 | #### Inherited from 26 | 27 | Omit.fromBlock 28 | 29 | ___ 30 | 31 | ### toBlock 32 | 33 | • `Optional` **toBlock**: `BlockTag` 34 | 35 | #### Inherited from 36 | 37 | Omit.toBlock 38 | 39 | ___ 40 | 41 | ### topics 42 | 43 | • `Optional` **topics**: (``null`` \| `string` \| `string`[])[] 44 | 45 | #### Inherited from 46 | 47 | Omit.topics 48 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: [pull_request, push] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [16.18.1] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js ${{ matrix.node }} 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: ${{ matrix.node }} 22 | cache: 'npm' 23 | - run: npm ci 24 | - run: npm test 25 | env: 26 | CI: true 27 | - run: npm run build 28 | - run: npm run no-git-changes 29 | -------------------------------------------------------------------------------- /docs/api/enums/ColonyRole.md: -------------------------------------------------------------------------------- 1 | # Enumeration: ColonyRole 2 | 3 | Available roles in the colonyNetwork. Find out more here: https://github.com/JoinColony/colonyNetwork/blob/develop/docs/_Docs_Permissions.md 4 | 5 | ## Enumeration Members 6 | 7 | ### Administration 8 | 9 | • **Administration** = ``6`` 10 | 11 | ___ 12 | 13 | ### Arbitration 14 | 15 | • **Arbitration** = ``2`` 16 | 17 | ___ 18 | 19 | ### Architecture 20 | 21 | • **Architecture** = ``3`` 22 | 23 | ___ 24 | 25 | ### ArchitectureSubdomain 26 | 27 | • **ArchitectureSubdomain** = ``4`` 28 | 29 | **`Deprecated`** 30 | 31 | The `ArchitectureSubdomain` role has been deprecated and should not be used 32 | 33 | ___ 34 | 35 | ### Funding 36 | 37 | • **Funding** = ``5`` 38 | 39 | ___ 40 | 41 | ### Recovery 42 | 43 | • **Recovery** = ``0`` 44 | 45 | ___ 46 | 47 | ### Root 48 | 49 | • **Root** = ``1`` 50 | -------------------------------------------------------------------------------- /docs/api/classes/ColonyGraph.md: -------------------------------------------------------------------------------- 1 | # Class: ColonyGraph 2 | 3 | ## Constructors 4 | 5 | ### constructor 6 | 7 | • **new ColonyGraph**(`colony`) 8 | 9 | Do not instantiate manually. Use the `graph` property on a Colony to access this class 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `colony` | [`Colony`](Colony.md) | 16 | 17 | ## Methods 18 | 19 | ### getTeamsWithMetadata 20 | 21 | ▸ **getTeamsWithMetadata**(): `Promise`<``null`` \| [`GraphDomain`](../interfaces/GraphDomain.md)[]\> 22 | 23 | Fetch all teams of a Colony including their Metadata 24 | 25 | **`Deprecated`** 26 | 27 | - will be replaced in v2.0 28 | 29 | This queries the Colony graph database for all teams including their metadata. The metadata is fetched from IPFS 30 | 31 | #### Returns 32 | 33 | `Promise`<``null`` \| [`GraphDomain`](../interfaces/GraphDomain.md)[]\> 34 | -------------------------------------------------------------------------------- /examples/browser/src/index.ts: -------------------------------------------------------------------------------- 1 | import '@picocss/pico'; 2 | 3 | import { ColonyRpcEndpoint } from '../../../src'; 4 | 5 | const addColonyRPC = () => { 6 | // If MetaMask is installed there will be an `ethereum` object on the `window` 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | (window as any).ethereum.request({ 9 | method: 'wallet_addEthereumChain', 10 | params: [ 11 | { 12 | chainId: '0x64', 13 | chainName: 'Gnosis Chain via Colony', 14 | nativeCurrency: { 15 | name: 'xDAI', 16 | symbol: 'XDAI', 17 | decimals: 18, 18 | }, 19 | rpcUrls: [ColonyRpcEndpoint.Gnosis], 20 | blockExplorerUrls: ['https://blockscout.com/xdai/mainnet'], 21 | }, 22 | ], 23 | }); 24 | }; 25 | 26 | document.querySelector('#add_rpc')?.addEventListener('click', addColonyRPC); 27 | -------------------------------------------------------------------------------- /docs/api/enums/ColonyRpcEndpoint.md: -------------------------------------------------------------------------------- 1 | # Enumeration: ColonyRpcEndpoint 2 | 3 | ## Enumeration Members 4 | 5 | ### Custom 6 | 7 | • **Custom** = ``""`` 8 | 9 | Colony's own RPC2 endpoint for a custom network 10 | 11 | ___ 12 | 13 | ### Gnosis 14 | 15 | • **Gnosis** = ``"https://xdai.colony.io/rpc/"`` 16 | 17 | Colony's own RPC2 endpoint for Gnosis chain 18 | 19 | ___ 20 | 21 | ### Goerli 22 | 23 | • **Goerli** = ``""`` 24 | 25 | Colony's own RPC2 endpoint for Goerli testnet 26 | 27 | ___ 28 | 29 | ### Mainnet 30 | 31 | • **Mainnet** = ``""`` 32 | 33 | Colony's own RPC2 endpoint for Mainnet 34 | 35 | ___ 36 | 37 | ### Xdai 38 | 39 | • **Xdai** = ``"https://xdai.colony.io/rpc/"`` 40 | 41 | Colony's own RPC2 endpoint for Gnosis chain (alias) 42 | 43 | ___ 44 | 45 | ### XdaiQa 46 | 47 | • **XdaiQa** = ``"https://xdai.colony.io/rpc/"`` 48 | 49 | Colony's own RPC2 endpoint for Gnosis chain (QA environment) 50 | -------------------------------------------------------------------------------- /examples/node/index.ts: -------------------------------------------------------------------------------- 1 | import { basename, resolve } from 'path'; 2 | import { fork } from 'child_process'; 3 | 4 | import { prompt } from 'inquirer'; 5 | import { sync as glob } from 'fast-glob'; 6 | import yargs from 'yargs/yargs'; 7 | import { hideBin } from 'yargs/helpers'; 8 | 9 | const { argv } = yargs(hideBin(process.argv)); 10 | 11 | if ('example' in argv) { 12 | fork(resolve(__dirname, `${argv.example}.ts`)); 13 | } else { 14 | const examples = glob(resolve(__dirname, './*.ts')) 15 | .map((path) => basename(path, '.ts')) 16 | .filter((example) => example !== 'index'); 17 | 18 | const questions = [ 19 | { 20 | type: 'list', 21 | name: 'example', 22 | message: 'Which example would you like to run?', 23 | choices: examples, 24 | }, 25 | ]; 26 | 27 | prompt(questions).then((answers) => { 28 | fork(resolve(__dirname, `${answers.example}.ts`)); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/ipfs/IpfsAdapter.ts: -------------------------------------------------------------------------------- 1 | export default interface IpfsAdapter { 2 | /** Name for the IpfsAdapter. All uppercase please */ 3 | name: string; 4 | 5 | /** 6 | * Should return the whole URL to an IPFS resource on the corresponding gateway (e.g. https://my-ipfs-gateway/ipfs/QmXxxxXXxxXxXxXxxxXXxxxXxXXx). 7 | * 8 | * @param cid - An IPFS hash (CID) 9 | * @returns The URL to an ipfs resource 10 | */ 11 | getIpfsUrl(cid: string): string; 12 | 13 | /** 14 | * Function to upload a JSON string to IPFS. Takes the string as an argument (use `JSON.stringify()` if needbe). Returns a promise that resolves to the IPFS hash (CID) 15 | * 16 | * @remarks This function should ideally **pin** your data on the relevant service. 17 | * 18 | * @param jsonString - JSON string to upload (and pin) to IPFS 19 | * @returns Promise to IPFS hash (CID) 20 | */ 21 | uploadJson(jsonString: string): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyNetworkOptions.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyNetworkOptions 2 | 3 | Additional options for the [ColonyNetwork](../classes/ColonyNetwork.md) 4 | 5 | ## Properties 6 | 7 | ### graphOptions 8 | 9 | • `Optional` **graphOptions**: [`SubgraphClientOptions`](SubgraphClientOptions.md) 10 | 11 | Provide custom GraphQL client options 12 | 13 | ___ 14 | 15 | ### ipfsAdapter 16 | 17 | • `Optional` **ipfsAdapter**: [`IpfsAdapter`](IpfsAdapter.md) 18 | 19 | Provide a custom [IpfsAdapter](IpfsAdapter.md) 20 | 21 | ___ 22 | 23 | ### metaTxBroadcasterEndpoint 24 | 25 | • `Optional` **metaTxBroadcasterEndpoint**: `string` 26 | 27 | Provide a custom metatransaction broadcaster endpoint 28 | 29 | ___ 30 | 31 | ### networkClientOptions 32 | 33 | • `Optional` **networkClientOptions**: [`NetworkClientOptions`](NetworkClientOptions.md) 34 | 35 | Provide custom [NetworkClientOptions](NetworkClientOptions.md) for the ColonyJS client 36 | -------------------------------------------------------------------------------- /examples/node/graph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraphClient, gql } from '../../src/graph'; 2 | 3 | const colonySubgraph = createSubgraphClient(); 4 | 5 | // This query fetches the latest DomainAdded event and prints all kinds of information about it 6 | const QUERY = gql` 7 | query SubgraphEvents { 8 | events(first: 10, where: { name_contains: "DomainAdded" }) { 9 | id 10 | address 11 | associatedColony { 12 | colonyAddress: id 13 | id: colonyChainId 14 | token { 15 | address: id 16 | decimals 17 | symbol 18 | } 19 | } 20 | transaction { 21 | hash: id 22 | block { 23 | id 24 | timestamp 25 | } 26 | } 27 | name 28 | args 29 | timestamp 30 | } 31 | } 32 | `; 33 | 34 | colonySubgraph 35 | .query(QUERY) 36 | .toPromise() 37 | .then((result) => { 38 | console.info(result.data.events[0]); 39 | }); 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please read [Code of Conduct](CODE_OF_CONDUCT.md) before contributing. 4 | 5 | ## Report Issues 6 | 7 | Report an issue using [GitHub Issues](https://github.com/JoinColony/colonySDK/issues). _Please fill out the template with as much detail as possible._ 8 | 9 | ## GitHub Workflow 10 | 11 | - Fork the repository 12 | 13 | - Clone the forked repository into your working directory 14 | 15 | - Create a new branch using the following naming schema: 16 | 17 | - [fix/feature/...]/[issue-#]-[description-in-kebab-case] 18 | 19 | - For example, `feature/6-add-connect-rinkeby-example` 20 | 21 | - Commit your changes using the following guidelines: 22 | 23 | - In the commit message, the first line must be capitalized 24 | 25 | - Keep your branch up to date using `rebase` instead of `merge` 26 | 27 | - Push your new fix/feature branch to your forked repository 28 | 29 | - Create a pull request and fill out the pull request template 30 | -------------------------------------------------------------------------------- /docs/api/interfaces/GraphDomain.md: -------------------------------------------------------------------------------- 1 | # Interface: GraphDomain 2 | 3 | ## Hierarchy 4 | 5 | - [`DomainMetadata`](DomainMetadata.md) 6 | 7 | ↳ **`GraphDomain`** 8 | 9 | ## Properties 10 | 11 | ### domainColor 12 | 13 | • `Optional` **domainColor**: `number` 14 | 15 | #### Inherited from 16 | 17 | [DomainMetadata](DomainMetadata.md).[domainColor](DomainMetadata.md#domaincolor) 18 | 19 | ___ 20 | 21 | ### domainName 22 | 23 | • `Optional` **domainName**: `string` 24 | 25 | #### Inherited from 26 | 27 | [DomainMetadata](DomainMetadata.md).[domainName](DomainMetadata.md#domainname) 28 | 29 | ___ 30 | 31 | ### domainPurpose 32 | 33 | • `Optional` **domainPurpose**: `string` 34 | 35 | #### Inherited from 36 | 37 | [DomainMetadata](DomainMetadata.md).[domainPurpose](DomainMetadata.md#domainpurpose) 38 | 39 | ___ 40 | 41 | ### id 42 | 43 | • **id**: `number` 44 | 45 | ___ 46 | 47 | ### metadata 48 | 49 | • **metadata**: `string` 50 | 51 | ___ 52 | 53 | ### name 54 | 55 | • **name**: `string` 56 | -------------------------------------------------------------------------------- /docs/api/enums/MetaTxBroadCasterEndpoint.md: -------------------------------------------------------------------------------- 1 | # Enumeration: MetaTxBroadCasterEndpoint 2 | 3 | ## Enumeration Members 4 | 5 | ### Custom 6 | 7 | • **Custom** = ``""`` 8 | 9 | The metatransaction broadcaster endpoint for a custom network 10 | 11 | ___ 12 | 13 | ### Gnosis 14 | 15 | • **Gnosis** = ``"https://xdai.colony.io/metatransaction/xdai"`` 16 | 17 | The metatransaction broadcaster endpoint on Gnosis Chain 18 | 19 | ___ 20 | 21 | ### Goerli 22 | 23 | • **Goerli** = ``""`` 24 | 25 | The metatransaction broadcaster endpoint on the Görli testnet 26 | 27 | ___ 28 | 29 | ### Mainnet 30 | 31 | • **Mainnet** = ``""`` 32 | 33 | The metatransaction broadcaster endpoint on mainnet 34 | 35 | ___ 36 | 37 | ### Xdai 38 | 39 | • **Xdai** = ``"https://xdai.colony.io/metatransaction/xdai"`` 40 | 41 | The metatransaction broadcaster endpoint on Gnosis Chain (alias) 42 | 43 | ___ 44 | 45 | ### XdaiQa 46 | 47 | • **XdaiQa** = ``"https://xdai.colony.io/metatransaction/xdai"`` 48 | 49 | The metatransaction broadcaster endpoint on Gnosis Chain (QA environment) 50 | -------------------------------------------------------------------------------- /examples/node/graph-subscription.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * A basic subscription within the Colony Subgraph. See urql's documentation here: https://formidable.com/open-source/urql/docs/advanced/subscriptions/#one-off-subscriptions for more information 3 | */ 4 | import { pipe, subscribe } from 'wonka'; 5 | 6 | import { createSubgraphClient, gql } from '../../src/graph'; 7 | 8 | const colonySubgraph = createSubgraphClient(); 9 | 10 | // Get the latest DomainAddedEvents across all Colonies 11 | const QUERY = gql` 12 | subscription DomainAddedEvents { 13 | events( 14 | orderDirection: desc 15 | orderBy: timestamp 16 | first: 1 17 | where: { name_contains: "DomainAdded" } 18 | ) { 19 | id 20 | address 21 | associatedColony { 22 | colonyAddress: id 23 | } 24 | name 25 | args 26 | timestamp 27 | } 28 | } 29 | `; 30 | 31 | console.info('Listening to new `DomainAdded` events...'); 32 | pipe( 33 | colonySubgraph.subscription(QUERY), 34 | subscribe((result) => { 35 | console.info(result.data.events[0]); 36 | }), 37 | ); 38 | -------------------------------------------------------------------------------- /docs/api/interfaces/TxCreatorConfig.md: -------------------------------------------------------------------------------- 1 | # Interface: TxCreatorConfig 2 | 3 | ## Type parameters 4 | 5 | | Name | 6 | | :------ | 7 | | `C` | 8 | | `M` | 9 | | `E` | 10 | | `MD` | 11 | 12 | ## Properties 13 | 14 | ### args 15 | 16 | • **args**: `unknown`[] \| () => `Promise`<`unknown`[]\> 17 | 18 | ___ 19 | 20 | ### colonyNetwork 21 | 22 | • **colonyNetwork**: [`ColonyNetwork`](../classes/ColonyNetwork.md) 23 | 24 | ___ 25 | 26 | ### contract 27 | 28 | • **contract**: `C` 29 | 30 | ___ 31 | 32 | ### eventData 33 | 34 | • `Optional` **eventData**: (`receipt`: `ContractReceipt`) => `Promise`<`E`\> 35 | 36 | #### Type declaration 37 | 38 | ▸ (`receipt`): `Promise`<`E`\> 39 | 40 | ##### Parameters 41 | 42 | | Name | Type | 43 | | :------ | :------ | 44 | | `receipt` | `ContractReceipt` | 45 | 46 | ##### Returns 47 | 48 | `Promise`<`E`\> 49 | 50 | ___ 51 | 52 | ### metadataType 53 | 54 | • `Optional` **metadataType**: `MD` 55 | 56 | ___ 57 | 58 | ### method 59 | 60 | • **method**: `M` 61 | 62 | ___ 63 | 64 | ### txConfig 65 | 66 | • `Optional` **txConfig**: [`TxConfig`](TxConfig.md)<`MD`\> 67 | -------------------------------------------------------------------------------- /src/ipfs/CloudflareReadonlyAdapter.ts: -------------------------------------------------------------------------------- 1 | import type IpfsAdapter from './IpfsAdapter'; 2 | 3 | /** 4 | * CloudflareReadonlyAdapter 5 | * 6 | * A Colony SDK IPFS adapter for Cloudflare IPFS (https://developers.cloudflare.com/web3/ipfs-gateway/). It only supports reading IPFS data from the Cloudflare gateway (not uploading or pinning files). 7 | * 8 | * This is the default IpfsAdapter used in Colony SDK. So in order to use this, you don't have to do anything. 9 | * 10 | */ 11 | class CloudflareReadonlyAdapter implements IpfsAdapter { 12 | private CLOUDFLARE_GATEWAY_ENDPOINT = 'https://cloudflare-ipfs.com/ipfs'; 13 | 14 | name = 'CLOUDFLARE'; 15 | 16 | getIpfsUrl(cid: string): string { 17 | return `${this.CLOUDFLARE_GATEWAY_ENDPOINT}/${cid}`; 18 | } 19 | 20 | // eslint-disable-next-line class-methods-use-this 21 | uploadJson(): Promise { 22 | throw new Error( 23 | // eslint-disable-next-line max-len 24 | 'The default Cloudflare IPFS adapter is readonly only. Please use another adapter to upload and pin files.', 25 | ); 26 | } 27 | } 28 | 29 | export default CloudflareReadonlyAdapter; 30 | -------------------------------------------------------------------------------- /docs/api/enums/TeamColor.md: -------------------------------------------------------------------------------- 1 | # Enumeration: TeamColor 2 | 3 | ## Enumeration Members 4 | 5 | ### Aqua 6 | 7 | • **Aqua** = ``10`` 8 | 9 | ___ 10 | 11 | ### Black 12 | 13 | • **Black** = ``2`` 14 | 15 | ___ 16 | 17 | ### Blue 18 | 19 | • **Blue** = ``4`` 20 | 21 | ___ 22 | 23 | ### BlueGrey 24 | 25 | • **BlueGrey** = ``11`` 26 | 27 | ___ 28 | 29 | ### EmeraldGreen 30 | 31 | • **EmeraldGreen** = ``3`` 32 | 33 | ___ 34 | 35 | ### Gold 36 | 37 | • **Gold** = ``9`` 38 | 39 | ___ 40 | 41 | ### Green 42 | 43 | • **Green** = ``7`` 44 | 45 | ___ 46 | 47 | ### LightPink 48 | 49 | • **LightPink** = ``0`` 50 | 51 | ___ 52 | 53 | ### Magenta 54 | 55 | • **Magenta** = ``14`` 56 | 57 | ___ 58 | 59 | ### Orange 60 | 61 | • **Orange** = ``13`` 62 | 63 | ___ 64 | 65 | ### Periwinkle 66 | 67 | • **Periwinkle** = ``8`` 68 | 69 | ___ 70 | 71 | ### Pink 72 | 73 | • **Pink** = ``1`` 74 | 75 | ___ 76 | 77 | ### Purple 78 | 79 | • **Purple** = ``12`` 80 | 81 | ___ 82 | 83 | ### PurpleGrey 84 | 85 | • **PurpleGrey** = ``15`` 86 | 87 | ___ 88 | 89 | ### Red 90 | 91 | • **Red** = ``6`` 92 | 93 | ___ 94 | 95 | ### Yellow 96 | 97 | • **Yellow** = ``5`` 98 | -------------------------------------------------------------------------------- /docs/api/enums/Extension.md: -------------------------------------------------------------------------------- 1 | # Enumeration: Extension 2 | 3 | Extension contract names 4 | 5 | ## Enumeration Members 6 | 7 | ### CoinMachine 8 | 9 | • **CoinMachine** = ``"CoinMachine"`` 10 | 11 | ___ 12 | 13 | ### EvaluatedExpenditure 14 | 15 | • **EvaluatedExpenditure** = ``"EvaluatedExpenditure"`` 16 | 17 | ___ 18 | 19 | ### FundingQueue 20 | 21 | • **FundingQueue** = ``"FundingQueue"`` 22 | 23 | ___ 24 | 25 | ### IVotingReputation 26 | 27 | • **IVotingReputation** = ``"IVotingReputation"`` 28 | 29 | ___ 30 | 31 | ### OneTxPayment 32 | 33 | • **OneTxPayment** = ``"OneTxPayment"`` 34 | 35 | ___ 36 | 37 | ### ReputationBootstrapper 38 | 39 | • **ReputationBootstrapper** = ``"ReputationBootstrapper"`` 40 | 41 | ___ 42 | 43 | ### StakedExpenditure 44 | 45 | • **StakedExpenditure** = ``"StakedExpenditure"`` 46 | 47 | ___ 48 | 49 | ### StreamingPayments 50 | 51 | • **StreamingPayments** = ``"StreamingPayments"`` 52 | 53 | ___ 54 | 55 | ### TokenSupplier 56 | 57 | • **TokenSupplier** = ``"TokenSupplier"`` 58 | 59 | ___ 60 | 61 | ### VotingReputation 62 | 63 | • **VotingReputation** = ``"VotingReputation"`` 64 | 65 | ___ 66 | 67 | ### Whitelist 68 | 69 | • **Whitelist** = ``"Whitelist"`` 70 | -------------------------------------------------------------------------------- /examples/node/roles.ts: -------------------------------------------------------------------------------- 1 | import { providers, Wallet } from 'ethers'; 2 | import { ColonyRole } from '@colony/colony-js'; 3 | 4 | import { ColonyNetwork, ColonyRpcEndpoint } from '../../src'; 5 | 6 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 7 | 8 | if (!process.env.PRIVATE_KEY) { 9 | throw new Error( 10 | `Please provide a valid private key as an environment variable: PRIVATE_KEY`, 11 | ); 12 | } 13 | 14 | const signer = new Wallet(process.env.PRIVATE_KEY as string).connect(provider); 15 | 16 | // Set the Administration and Funding permissions for the OneTxPayment extension of a Colony 17 | const start = async () => { 18 | const colonyNetwork = await ColonyNetwork.init(signer); 19 | const colony = await colonyNetwork.getColony( 20 | '0xA6fD5655c1249f1349D0917E732813Ebd9439A54', 21 | ); 22 | // await colony 23 | // .setRoles('0xA75b108808584A15ceEbF8f6CAc19EaD91cAbCd2', [ 24 | // ColonyRole.Administration, 25 | // ColonyRole.Funding, 26 | // ]) 27 | // .tx(); 28 | const roles = await colony.getRoles( 29 | '0xA75b108808584A15ceEbF8f6CAc19EaD91cAbCd2', 30 | ); 31 | console.info( 32 | 'Roles', 33 | roles.map((role) => ColonyRole[role]), 34 | ); 35 | }; 36 | 37 | start(); 38 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "index" 3 | sidebar_position: 0 4 | --- 5 | 6 | # Colony SDK 7 | 8 | ![](https://raw.githubusercontent.com/JoinColony/brand/v1.0.0/logo_sdk.svg) 9 | 10 | The Colony SDK is a community project aiming to provide a quick and easy way to get started with building on top of Colony Network contracts. 11 | 12 | The goal is to simplify the complexity of the contract's functions to serve the most popular functions using an easy-to-understand programming interface while providing sane defaults and fallbacks. We also provide various examples for various applications to get you up and building with Colony in no time! 13 | Since version 1.0 it covers _everything_ the dApp can do, so you'll be able to run your DAO entirely programmatically 👩‍💻 14 | 15 | If you're impatient to start building, check out the [Getting Started](getting-started/index.md) pages. 16 | 17 | If you're already thinking that the feature set of Colony SDK might be too limiting for your use case, you might want to check out its bigger sibling, [ColonyJS](https://docs.colony.io/colonyjs), the almighty Colony API reference implementation (in TypeScript). 18 | 19 | The Colony SDK is under continuous development, with new features constantly being added, and should cover most common use cases. 20 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyFilter.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyFilter 2 | 3 | A Colony extended ethers Filter to keep track of where events are coming from 4 | 5 | ## Hierarchy 6 | 7 | - [`Ethers6Filter`](Ethers6Filter.md) 8 | 9 | ↳ **`ColonyFilter`** 10 | 11 | ↳↳ [`ColonyEvent`](ColonyEvent.md) 12 | 13 | ## Properties 14 | 15 | ### address 16 | 17 | • `Optional` **address**: `string` \| `string`[] 18 | 19 | #### Inherited from 20 | 21 | [Ethers6Filter](Ethers6Filter.md).[address](Ethers6Filter.md#address) 22 | 23 | ___ 24 | 25 | ### eventName 26 | 27 | • **eventName**: `string` 28 | 29 | The full event signature of this event (e.g. `TokenMinted(uint256))` 30 | 31 | ___ 32 | 33 | ### eventSource 34 | 35 | • **eventSource**: keyof [`EventSources`](EventSources.md) 36 | 37 | The Colony contract the event originated from 38 | 39 | ___ 40 | 41 | ### fromBlock 42 | 43 | • `Optional` **fromBlock**: `BlockTag` 44 | 45 | #### Inherited from 46 | 47 | [Ethers6Filter](Ethers6Filter.md).[fromBlock](Ethers6Filter.md#fromblock) 48 | 49 | ___ 50 | 51 | ### toBlock 52 | 53 | • `Optional` **toBlock**: `BlockTag` 54 | 55 | #### Inherited from 56 | 57 | [Ethers6Filter](Ethers6Filter.md).[toBlock](Ethers6Filter.md#toblock) 58 | 59 | ___ 60 | 61 | ### topics 62 | 63 | • `Optional` **topics**: (``null`` \| `string` \| `string`[])[] 64 | 65 | #### Inherited from 66 | 67 | [Ethers6Filter](Ethers6Filter.md).[topics](Ethers6Filter.md#topics) 68 | -------------------------------------------------------------------------------- /docs/guides/metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: A guide on how to handle IPFS metadata in Colony. We explore how to use a different IPFS adapter to let Colony SDK do the heavy lifting. 3 | 4 | sidebar_position: 2 5 | --- 6 | 7 | # Metadata within Colony 8 | 9 | Storing data on chain is expensive. Metadata is stored on IPFS and our solution to enrich on-chain data with additional information and only used in very few methods around Colony. We also provide ways to automatically create and store metadata for your transaction. 10 | 11 | Metadata is stored on IPFS and needs to be formatted in a certain way. There are multiple ways of handling metadata: 12 | 13 | ## Manual creation of the data as JSON object 14 | 15 | To create the metadata objects manually you can use the `@colony/colony-event-metadata-parser`, package which can de(serialize) the data for you in the required format. Read more about it [here](https://github.com/JoinColony/ColonyEventMetadataParser). Once you have the stringified data, you'd upload it to IPFS using your preferred upload and pinning method. 16 | 17 | ## Let Colony SDK take care of it 18 | 19 | For this an upload/pin compatible [`IpfsAdapter`](../api/interfaces/IpfsAdapter.md) has to be used (like the [`PinataIpfsAdapter`](../api/classes/PinataAdapter.md)). With that you can provide the metadata as a JavaScript object to the relevant method and Colony SDK will take care of proper serialization and pinning/uploading it to IPFS. 20 | 21 | -------------------------------------------------------------------------------- /docs/api/interfaces/IpfsAdapter.md: -------------------------------------------------------------------------------- 1 | # Interface: IpfsAdapter 2 | 3 | ## Implemented by 4 | 5 | - [`CloudflareReadonlyAdapter`](../classes/CloudflareReadonlyAdapter.md) 6 | - [`PinataAdapter`](../classes/PinataAdapter.md) 7 | 8 | ## Properties 9 | 10 | ### name 11 | 12 | • **name**: `string` 13 | 14 | Name for the IpfsAdapter. All uppercase please 15 | 16 | ## Methods 17 | 18 | ### getIpfsUrl 19 | 20 | ▸ **getIpfsUrl**(`cid`): `string` 21 | 22 | Should return the whole URL to an IPFS resource on the corresponding gateway (e.g. https://my-ipfs-gateway/ipfs/QmXxxxXXxxXxXxXxxxXXxxxXxXXx). 23 | 24 | #### Parameters 25 | 26 | | Name | Type | Description | 27 | | :------ | :------ | :------ | 28 | | `cid` | `string` | An IPFS hash (CID) | 29 | 30 | #### Returns 31 | 32 | `string` 33 | 34 | The URL to an ipfs resource 35 | 36 | ___ 37 | 38 | ### uploadJson 39 | 40 | ▸ **uploadJson**(`jsonString`): `Promise`<`string`\> 41 | 42 | Function to upload a JSON string to IPFS. Takes the string as an argument (use `JSON.stringify()` if needbe). Returns a promise that resolves to the IPFS hash (CID) 43 | 44 | **`Remarks`** 45 | 46 | This function should ideally **pin** your data on the relevant service. 47 | 48 | #### Parameters 49 | 50 | | Name | Type | Description | 51 | | :------ | :------ | :------ | 52 | | `jsonString` | `string` | JSON string to upload (and pin) to IPFS | 53 | 54 | #### Returns 55 | 56 | `Promise`<`string`\> 57 | 58 | Promise to IPFS hash (CID) 59 | -------------------------------------------------------------------------------- /examples/browser/web/advanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - create a team, move funds and make a payment

11 |

1) Let's connect to the Colony first Make sure you have the FUNDING and 12 | ADMINISTRATION permissions in the Root domain of that Colony

13 | 14 | 15 |

2) Next we create a team

16 | 17 |

3) We need to move some funds to the new team. Make sure the root colony has at least 18 | about 1 unit of the Colony's native token (see message below after connecting)

19 | 20 |

4) Then, within that team, send someone some of the Colony's native tokens

21 | 22 | 23 |
24 |
25 | Message 26 |
27 |

28 |

29 |
30 | 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/browser/web/local-basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - local deployment - check the MetaColony's funding

11 |

First make sure that the Colony contracts are deployed locally and the Reputation Oracle is set up. See here for 13 | more 14 | information

15 |

We'll be using wallet address 0xb77D57F4959eAfA0339424b83FcFaf9c15407461 to connect to the locally deployed 16 | MetaColony. Any transaction will automatically be signed as we have the private key hardcoded. No connection 17 | to 18 | MetaMask is needed.

19 |

Let's check the CLNY funding for the local MetaColony. Make sure to provide the 20 | correct 21 | address given in the `etherrouter-address.json` in the `colonyNetwork` directory.

22 | 23 | 24 | 25 |
26 |
27 | Message 28 |
29 |

30 |

31 |
32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Filter, 3 | FilterByBlockHash, 4 | TransactionReceipt, 5 | } from '@ethersproject/abstract-provider'; 6 | import type { LogDescription } from '@ethersproject/abi'; 7 | 8 | /** 9 | * Custom Transaction receipt for when we manually have to parse logs (metatransactions) 10 | */ 11 | export interface ParsedLogTransactionReceipt extends TransactionReceipt { 12 | parsedLogs: LogDescription[]; 13 | } 14 | 15 | /** 16 | * Ethers 6 supports mulitple addresses in a filter. Until then we have this 17 | */ 18 | export interface Ethers6Filter extends Omit { 19 | address?: string | string[]; 20 | } 21 | 22 | /** 23 | * Ethers 6 supports mulitple addresses in a filter. Until then we have this 24 | */ 25 | export interface Ethers6FilterByBlockHash 26 | extends Omit { 27 | address?: string | string[]; 28 | } 29 | 30 | /** 31 | * @internal 32 | * Type helper. Extracts all parameter types 33 | */ 34 | /* eslint-disable @typescript-eslint/no-explicit-any */ 35 | export type Parameters = F extends (...rest: infer R) => any ? R : never; 36 | 37 | /** 38 | * @internal 39 | * Type helper. Extracts all parameter types except the first two 40 | * Mostly used for removing the proof params from contract transaction methods 41 | */ 42 | export type ParametersFrom2 = F extends ( 43 | arg0: any, 44 | arg1: any, 45 | ...rest: infer R 46 | ) => any 47 | ? R 48 | : never; 49 | /* eslint-enable @typescript-eslint/no-explicit-any */ 50 | 51 | /** 52 | * @internal 53 | * Type helper. Forces TypeScript to expand the contents in the docs 54 | */ 55 | // eslint-disable-next-line @typescript-eslint/ban-types 56 | export type Expand = {} & { [P in keyof T]: T[P] }; 57 | -------------------------------------------------------------------------------- /examples/node/automation.ts: -------------------------------------------------------------------------------- 1 | import { providers, Wallet } from 'ethers'; 2 | 3 | import { ColonyNetwork, ColonyRpcEndpoint, w } from '../../src'; 4 | 5 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 6 | 7 | if (!process.env.PRIVATE_KEY) { 8 | throw new Error('Please provide PRIVATE_KEY as an environment variable'); 9 | } 10 | 11 | const start = async () => { 12 | // Create a new wallet from a private key. This key is stored in an environment variable called `PRIATE_KEY` 13 | const wallet = new Wallet(process.env.PRIVATE_KEY as string).connect( 14 | provider, 15 | ); 16 | 17 | // Initialize the Colony Network 18 | const colonyNetwork = await ColonyNetwork.init(wallet); 19 | // Get an instance of your favourite colony 20 | const colony = await colonyNetwork.getColony( 21 | '0x364B3153A24bb9ECa28B8c7aCeB15E3942eb4fc5', 22 | ); 23 | 24 | // Define recipients and amounts to pay out 25 | const recipients = [ 26 | '0xf24e6667C49f6272A7EbFdf4e5e7e9BBda0c89Bc', 27 | '0x7E676370a053Dcfa851DCA951E9a9F567503E33B', 28 | '0xD2c5D6D1f16d9B1e8bbD0e2EA784a67dC6aC8969', 29 | '0x3962e6380421c94d69584e69255EAd67C92BFD23', 30 | ]; 31 | const amounts = [1, 2, 3, 4]; 32 | 33 | // Mint 10 of the colony's native token 34 | await colony.mint(w`10`).metaTx(); 35 | // Claim these tokens for the colony 36 | await colony.claimFunds().metaTx(); 37 | // Move all the funds from the Root team to the team with the number 2 38 | await colony.moveFundsToTeam(w`10`, 2).metaTx(); 39 | if (colony.ext.oneTx) { 40 | // Pay a bunch of addresses the respective of the colony's native token from the funds of team 2 41 | await colony.ext.oneTx.pay(recipients, amounts, 2).metaTx(); 42 | } 43 | }; 44 | 45 | start(); 46 | -------------------------------------------------------------------------------- /examples/browser/src/metamask.ts: -------------------------------------------------------------------------------- 1 | import { providers, Signer } from 'ethers'; 2 | import { ColonyNetwork } from '../../../src'; 3 | 4 | // If MetaMask is installed there will be an `ethereum` object on the `window` 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | const provider = new providers.Web3Provider((window as any).ethereum); 7 | 8 | // Get the Colony's XDAI funding in the ROOT pot (id 1) 9 | const getMetaColony = async (signer: Signer) => { 10 | const colonyNetwork = await ColonyNetwork.init(signer); 11 | return colonyNetwork.getMetaColony(); 12 | }; 13 | 14 | // As soon as we issue `eth_requestAccounts`, MetaMask will prompt the user to allow a connection to the site 15 | // We then return the `signer` that is provided by MetaMask which will act as the user's wallet 16 | const connect = async () => { 17 | await provider.send('eth_requestAccounts', []); 18 | return provider.getSigner(); 19 | }; 20 | 21 | // Just some basic setup to display the UI 22 | const button = document.querySelector('#button'); 23 | const errElm: HTMLParagraphElement | null = document.querySelector('#error'); 24 | const resultElm: HTMLParagraphElement | null = 25 | document.querySelector('#result'); 26 | 27 | if (!button || !errElm || !resultElm) { 28 | throw new Error('Could not find all required HTML elements'); 29 | } 30 | 31 | const panik = (err: string) => { 32 | errElm.innerText = err; 33 | }; 34 | const kalm = () => { 35 | errElm.innerText = ''; 36 | }; 37 | const speak = (msg: string) => { 38 | resultElm.innerText = msg; 39 | }; 40 | 41 | button.addEventListener('click', async () => { 42 | kalm(); 43 | speak('Thinking...'); 44 | try { 45 | const signer = await connect(); 46 | const metaColonyClient = await getMetaColony(signer); 47 | speak( 48 | `Connected to metaColonyClient with version ${metaColonyClient.version}`, 49 | ); 50 | } catch (e) { 51 | panik(`Found an error: ${(e as Error).message}`); 52 | speak(''); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /examples/browser/src/basic.ts: -------------------------------------------------------------------------------- 1 | import { providers, utils } from 'ethers'; 2 | 3 | import { ColonyNetwork, ColonyRpcEndpoint, Tokens } from '../../../src'; 4 | 5 | const { formatEther, isAddress } = utils; 6 | 7 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 8 | 9 | // Get the Colony's CLNY funding in the root domain (on Gnosis chain) 10 | const getColonyFunding = async (colonyAddress: string) => { 11 | const colonyNetwork = await ColonyNetwork.init(provider); 12 | const colony = await colonyNetwork.getColony(colonyAddress); 13 | const funding = await colony.getBalance(Tokens.Gnosis.CLNY); 14 | return formatEther(funding); 15 | }; 16 | 17 | // Just some basic setup to display the UI 18 | const addressInput: HTMLInputElement | null = 19 | document.querySelector('#address'); 20 | const button = document.querySelector('#button'); 21 | const errElm: HTMLParagraphElement | null = document.querySelector('#error'); 22 | const resultElm: HTMLParagraphElement | null = 23 | document.querySelector('#result'); 24 | 25 | if (!addressInput || !button || !errElm || !resultElm) { 26 | throw new Error('Could not find all required HTML elements'); 27 | } 28 | 29 | const panik = (err: string) => { 30 | errElm.innerText = err; 31 | }; 32 | const kalm = () => { 33 | errElm.innerText = ''; 34 | }; 35 | const speak = (msg: string) => { 36 | resultElm.innerText = msg; 37 | }; 38 | 39 | button.addEventListener('click', async () => { 40 | kalm(); 41 | const colonyAddress = addressInput?.value; 42 | if (!isAddress(colonyAddress)) { 43 | return panik('This is not a valid address'); 44 | } 45 | speak('Thinking...'); 46 | addressInput.value = ''; 47 | let funding: string; 48 | try { 49 | funding = await getColonyFunding(colonyAddress); 50 | speak( 51 | `${funding} CLNY in root domain of Colony with address: ${colonyAddress}`, 52 | ); 53 | } catch (e) { 54 | panik(`Found an error: ${(e as Error).message}`); 55 | speak(''); 56 | } 57 | return null; 58 | }); 59 | -------------------------------------------------------------------------------- /examples/browser/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demos

11 |

See colonySDK and colonyStarter for more information.

13 |

Check out the source code for the examples here.

15 |

These demos interact with LIVE Colonies on Gnosis chain. We recommend to use the Colony 16 | RPC endpoint directly.

17 | 34 |

These demos interact with a local development RPC endpoint (see here on how to set 36 | it up). MetaMask is not needed, the examples provide a wallet account.

37 | 48 |
49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", 5 | "@colony/eslint-config-colony" 6 | ], 7 | "plugins": [ 8 | "@typescript-eslint", 9 | "eslint-plugin-tsdoc" 10 | ], 11 | "env": { 12 | "browser": true, 13 | "node": true 14 | }, 15 | "rules": { 16 | "camelcase": [ 17 | "error", 18 | { 19 | "allow": [ 20 | ".+_EventFilter$", 21 | ".+_EventObject$", 22 | ".+__factory" 23 | ] 24 | } 25 | ], 26 | "no-dupe-class-members": "off", 27 | "no-redeclare": "off", 28 | "no-shadow": "off", 29 | "no-unused-vars": "off", 30 | "no-use-before-define": "off", 31 | "@typescript-eslint/no-unused-vars": "error", 32 | "@typescript-eslint/no-shadow": [ 33 | "error" 34 | ], 35 | "import/extensions": "off", 36 | "import/no-unresolved": "off", 37 | "import/prefer-default-export": "off", 38 | "import/no-extraneous-dependencies": [ 39 | "error", 40 | { 41 | "devDependencies": [ 42 | "scripts/*.ts", 43 | "src/__tests__/*.ts", 44 | "examples/node/*.ts", 45 | "examples/browser/src/*.ts" 46 | ] 47 | } 48 | ], 49 | "tsdoc/syntax": "warn" 50 | }, 51 | "overrides": [ 52 | { 53 | "files": [ 54 | "**/__tests__/*.ts" 55 | ], 56 | "env": { 57 | "jest": true 58 | } 59 | }, 60 | { 61 | "files": [ 62 | "**/contracts/**/*.ts" 63 | ], 64 | "rules": { 65 | "eslint-comments/disable-enable-pair": "off", 66 | "eslint-comments/no-unlimited-disable": "off" 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /docs/api/classes/CloudflareReadonlyAdapter.md: -------------------------------------------------------------------------------- 1 | # Class: CloudflareReadonlyAdapter 2 | 3 | CloudflareReadonlyAdapter 4 | 5 | A Colony SDK IPFS adapter for Cloudflare IPFS (https://developers.cloudflare.com/web3/ipfs-gateway/). It only supports reading IPFS data from the Cloudflare gateway (not uploading or pinning files). 6 | 7 | This is the default IpfsAdapter used in Colony SDK. So in order to use this, you don't have to do anything. 8 | 9 | ## Implements 10 | 11 | - [`IpfsAdapter`](../interfaces/IpfsAdapter.md) 12 | 13 | ## Constructors 14 | 15 | ### constructor 16 | 17 | • **new CloudflareReadonlyAdapter**() 18 | 19 | ## Properties 20 | 21 | ### name 22 | 23 | • **name**: `string` = `'CLOUDFLARE'` 24 | 25 | Name for the IpfsAdapter. All uppercase please 26 | 27 | #### Implementation of 28 | 29 | [IpfsAdapter](../interfaces/IpfsAdapter.md).[name](../interfaces/IpfsAdapter.md#name) 30 | 31 | ## Methods 32 | 33 | ### getIpfsUrl 34 | 35 | ▸ **getIpfsUrl**(`cid`): `string` 36 | 37 | Should return the whole URL to an IPFS resource on the corresponding gateway (e.g. https://my-ipfs-gateway/ipfs/QmXxxxXXxxXxXxXxxxXXxxxXxXXx). 38 | 39 | #### Parameters 40 | 41 | | Name | Type | Description | 42 | | :------ | :------ | :------ | 43 | | `cid` | `string` | An IPFS hash (CID) | 44 | 45 | #### Returns 46 | 47 | `string` 48 | 49 | The URL to an ipfs resource 50 | 51 | #### Implementation of 52 | 53 | [IpfsAdapter](../interfaces/IpfsAdapter.md).[getIpfsUrl](../interfaces/IpfsAdapter.md#getipfsurl) 54 | 55 | ___ 56 | 57 | ### uploadJson 58 | 59 | ▸ **uploadJson**(): `Promise`<`string`\> 60 | 61 | Function to upload a JSON string to IPFS. Takes the string as an argument (use `JSON.stringify()` if needbe). Returns a promise that resolves to the IPFS hash (CID) 62 | 63 | **`Remarks`** 64 | 65 | This function should ideally **pin** your data on the relevant service. 66 | 67 | #### Returns 68 | 69 | `Promise`<`string`\> 70 | 71 | Promise to IPFS hash (CID) 72 | 73 | #### Implementation of 74 | 75 | [IpfsAdapter](../interfaces/IpfsAdapter.md).[uploadJson](../interfaces/IpfsAdapter.md#uploadjson) 76 | -------------------------------------------------------------------------------- /src/graph/ColonyGraph.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DomainMetadata, 3 | MetadataType, 4 | } from '@colony/colony-event-metadata-parser'; 5 | import { gql } from '@urql/core'; 6 | 7 | import { Colony } from '../ColonyNetwork'; 8 | 9 | export interface GraphDomain extends DomainMetadata { 10 | id: number; 11 | name: string; 12 | metadata: string; 13 | } 14 | 15 | export default class ColonyGraph { 16 | private colony: Colony; 17 | 18 | /** 19 | * Do not instantiate manually. Use the `graph` property on a Colony to access this class 20 | */ 21 | constructor(colony: Colony) { 22 | this.colony = colony; 23 | } 24 | 25 | /** 26 | * Fetch all teams of a Colony including their Metadata 27 | * 28 | * @deprecated - will be replaced in v2.0 29 | * 30 | * This queries the Colony graph database for all teams including their metadata. The metadata is fetched from IPFS 31 | */ 32 | async getTeamsWithMetadata() { 33 | const query = gql` 34 | query Domains($colonyAddress: String!) { 35 | domains(where: { colonyAddress: $colonyAddress }) { 36 | id: domainChainId 37 | name 38 | metadata 39 | } 40 | } 41 | `; 42 | const colonyAddress = this.colony.address.toLowerCase(); 43 | const result = await this.colony.colonyNetwork.graphClient 44 | .query(query, { colonyAddress }) 45 | .toPromise(); 46 | if (result && result.data) { 47 | const metadataPromises = result.data.domains.map( 48 | (domain: GraphDomain) => { 49 | if (!domain.metadata) { 50 | return Promise.resolve({}); 51 | } 52 | return this.colony.colonyNetwork.ipfs.getMetadata( 53 | MetadataType.Domain, 54 | domain.metadata, 55 | ); 56 | }, 57 | ); 58 | const metadata = await Promise.all(metadataPromises); 59 | return result.data.domains.map((domain: GraphDomain, idx: number) => ({ 60 | ...domain, 61 | ...metadata[idx], 62 | id: parseInt(domain.id as unknown as string, 10), 63 | })) as GraphDomain[]; 64 | } 65 | 66 | return null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ipfs/PinataAdapter.ts: -------------------------------------------------------------------------------- 1 | import type IpfsAdapter from './IpfsAdapter'; 2 | 3 | const COLONY_IPFS_PINATA_TOKEN = 4 | typeof global != 'undefined' && global.process 5 | ? global.process.env.COLONY_IPFS_PINATA_TOKEN 6 | : undefined; 7 | 8 | /** 9 | * A Colony SDK IPFS adapter for Pinata (https://pinata.cloud) 10 | * 11 | * In order to use this, sign up for Pinata (if you haven't already) and generate a token. Then either supply this token when instantiating the class (example below) or provide it via the environment variable `COLONY_IPFS_PINATA_TOKEN` (when using NodeJS). Then provide an instance of this class to the [[ColonyNetwork]] or [[ColonyEventManager]] classes (depending on your needs). 12 | * 13 | * :::danger Tokens are sensitive data 14 | * Do not check in your Pinata token into version control and **DO NOT EMBED IT INTO YOUR FRONTEND BUNDLE**. 15 | * ::: 16 | * 17 | * @example 18 | * ```typescript 19 | * import { ColonyNetwork, PinataAdapter } from '@colony/sdk'; 20 | * const pinataAdapter = new PinataAdapter('[YOUR_PINANTA_JWT_TOKEN]'); 21 | * // Immediately executing async function 22 | * (async function() { 23 | * const colonyNetwork = ColonyNetwork.init(signerOrProvider, { ipfsAdapter: pinataAdapter }); 24 | * })(); 25 | * ``` 26 | */ 27 | class PinataAdapter implements IpfsAdapter { 28 | private token: string; 29 | 30 | private PINATA_GATEWAY_ENDPOINT = 'https://gateway.pinata.cloud/ipfs'; 31 | 32 | name = 'PINATA'; 33 | 34 | constructor(pinataToken?: string) { 35 | const token = pinataToken || COLONY_IPFS_PINATA_TOKEN; 36 | if (!token) { 37 | throw new Error( 38 | `Cannot find pinata token. Please supply it as an argument to the class or as "process.env.COLONY_IPFS_PINATA_TOKEN (in NodeJS)"`, 39 | ); 40 | } 41 | this.token = token; 42 | } 43 | 44 | getIpfsUrl(cid: string): string { 45 | return `${this.PINATA_GATEWAY_ENDPOINT}/${cid}`; 46 | } 47 | 48 | async uploadJson(jsonString: string): Promise { 49 | const res = await fetch('https://api.pinata.cloud/pinning/pinJSONToIPFS', { 50 | method: 'POST', 51 | headers: { 52 | Accept: 'application/json', 53 | 'Content-Type': 'application/json', 54 | Authorization: `Bearer ${this.token}`, 55 | }, 56 | body: JSON.stringify({ 57 | pinataContent: JSON.parse(jsonString), 58 | }), 59 | }); 60 | const parsed = await res.json(); 61 | return parsed.IpfsHash; 62 | } 63 | } 64 | 65 | export default PinataAdapter; 66 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export { ColonyRole, Extension, Id, Network, Tokens } from '@colony/colony-js'; 2 | 3 | export enum MetaTxBroadCasterEndpoint { 4 | /** The metatransaction broadcaster endpoint on mainnet */ 5 | Mainnet = '', 6 | /** The metatransaction broadcaster endpoint on the Görli testnet */ 7 | Goerli = '', 8 | /** The metatransaction broadcaster endpoint on Gnosis Chain */ 9 | Gnosis = 'https://xdai.colony.io/metatransaction/xdai', 10 | /** The metatransaction broadcaster endpoint on Gnosis Chain (alias) */ 11 | Xdai = 'https://xdai.colony.io/metatransaction/xdai', 12 | /** The metatransaction broadcaster endpoint on Gnosis Chain (QA environment) */ 13 | XdaiQa = 'https://xdai.colony.io/metatransaction/xdai', 14 | /** The metatransaction broadcaster endpoint for a custom network */ 15 | Custom = '', 16 | } 17 | 18 | export enum ColonyRpcEndpoint { 19 | /** Colony's own RPC2 endpoint for Mainnet */ 20 | Mainnet = '', 21 | /** Colony's own RPC2 endpoint for Goerli testnet */ 22 | Goerli = '', 23 | /** Colony's own RPC2 endpoint for Gnosis chain */ 24 | Gnosis = 'https://xdai.colony.io/rpc/', 25 | /** Colony's own RPC2 endpoint for Gnosis chain (alias) */ 26 | Xdai = 'https://xdai.colony.io/rpc/', 27 | /** Colony's own RPC2 endpoint for Gnosis chain (QA environment) */ 28 | XdaiQa = 'https://xdai.colony.io/rpc/', 29 | /** Colony's own RPC2 endpoint for a custom network */ 30 | Custom = '', 31 | } 32 | 33 | // TODO: Consider moving this to the metadata-parser 34 | export enum TeamColor { 35 | LightPink, 36 | Pink, 37 | Black, 38 | EmeraldGreen, 39 | Blue, 40 | Yellow, 41 | Red, 42 | Green, 43 | Periwinkle, 44 | Gold, 45 | Aqua, 46 | BlueGrey, 47 | Purple, 48 | Orange, 49 | Magenta, 50 | PurpleGrey, 51 | } 52 | 53 | export enum ColonyLabelSuffix { 54 | Mainnet = '.colony.joincolony.eth', 55 | Goerli = '.colony.joincolony.test', 56 | Gnosis = '.colony.joincolony.colonyxdai', 57 | Xdai = '.colony.joincolony.colonyxdai', 58 | XdaiQa = '.colony.joincolony.colonyxdai', 59 | Custom = '.colony.joincolony.test', 60 | } 61 | 62 | export enum UserLabelSuffix { 63 | Mainnet = '.user.joincolony.eth', 64 | Goerli = '.user.joincolony.test', 65 | Gnosis = '.user.joincolony.colonyxdai', 66 | Xdai = '.user.joincolony.colonyxdai', 67 | XdaiQa = '.user.joincolony.colonyxdai', 68 | Custom = '.user.joincolony.test', 69 | } 70 | 71 | /** 72 | * Identifies a motion as a decision 73 | * 74 | * Usually there's no need to use this directly. Use [[VotingReputation.createDecision]] instead. 75 | */ 76 | export const DecisionMotionCode = '0x12345678'; 77 | -------------------------------------------------------------------------------- /examples/browser/src/local-basic.ts: -------------------------------------------------------------------------------- 1 | import { providers, utils, Wallet } from 'ethers'; 2 | 3 | import { ColonyNetwork } from '../../../src'; 4 | 5 | const { formatEther, isAddress } = utils; 6 | 7 | const provider = new providers.JsonRpcProvider('http://127.0.0.1:8545'); 8 | 9 | const getWallet = () => { 10 | // This is the private key of the ganache account with index 0: 0xb77D57F4959eAfA0339424b83FcFaf9c15407461. In the contract deployments done with truffle this account is used as the owner of the MetaColony, so we have all the permissions. This will effectively replace MetaMask 11 | return new Wallet( 12 | '0x0355596cdb5e5242ad082c4fe3f8bbe48c9dba843fe1f99dd8272f487e70efae', 13 | provider, 14 | ); 15 | }; 16 | 17 | // Instantiate a colony client, connected to the local MetaColony and Reputation Oracle 18 | // Then get the Colony's CLNY funding in the root domain 19 | const getMetaColonyFunding = async (networkAddress: string) => { 20 | // Get the ethers wallet 21 | const signer = getWallet(); 22 | // Connect to a locally deployed ColonyNetwork (EtherRouter address), with a local Reputation Oracle running on port 3000 23 | const colonyNetwork = await ColonyNetwork.init(signer, { 24 | networkClientOptions: { 25 | networkAddress, 26 | reputationOracleEndpoint: 'http://localhost:3000', 27 | }, 28 | }); 29 | // Get the locally deployed MetaColony... 30 | const colony = await colonyNetwork.getMetaColony(); 31 | // ...and retrieve its CLNY funding 32 | const funding = await colony.getBalance(); 33 | return formatEther(funding); 34 | }; 35 | 36 | // Just some basic setup to display the UI 37 | const addressInput: HTMLInputElement | null = 38 | document.querySelector('#address'); 39 | const button = document.querySelector('#button'); 40 | const errElm: HTMLParagraphElement | null = document.querySelector('#error'); 41 | const resultElm: HTMLParagraphElement | null = 42 | document.querySelector('#result'); 43 | 44 | if (!addressInput || !button || !errElm || !resultElm) { 45 | throw new Error('Could not find all required HTML elements'); 46 | } 47 | 48 | const panik = (err: string) => { 49 | errElm.innerText = err; 50 | }; 51 | const kalm = () => { 52 | errElm.innerText = ''; 53 | }; 54 | const speak = (msg: string) => { 55 | resultElm.innerText = msg; 56 | }; 57 | 58 | button.addEventListener('click', async () => { 59 | kalm(); 60 | const etherRouterAddress = addressInput.value; 61 | if (!isAddress(etherRouterAddress)) { 62 | return panik('This is not a valid address'); 63 | } 64 | speak('Thinking...'); 65 | addressInput.value = ''; 66 | let funding: string; 67 | try { 68 | funding = await getMetaColonyFunding(etherRouterAddress); 69 | speak(`${funding} CLNY in root domain of local MetaColony`); 70 | } catch (e) { 71 | panik(`Found an error: ${(e as Error).message}`); 72 | speak(''); 73 | } 74 | return null; 75 | }); 76 | -------------------------------------------------------------------------------- /examples/node/create.ts: -------------------------------------------------------------------------------- 1 | import { providers, Wallet } from 'ethers'; 2 | 3 | import { 4 | ColonyNetwork, 5 | ColonyRole, 6 | ColonyRpcEndpoint, 7 | ColonyToken, 8 | SupportedExtension, 9 | } from '../../src'; 10 | 11 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 12 | 13 | if (!process.env.PRIVATE_KEY) { 14 | throw new Error( 15 | `Please provide a valid private key as an environment variable: PRIVATE_KEY`, 16 | ); 17 | } 18 | 19 | // Deploy a Colony with everything that's needed 20 | const start = async () => { 21 | const signer = new Wallet(process.env.PRIVATE_KEY as string).connect( 22 | provider, 23 | ); 24 | const colonyNetwork = await ColonyNetwork.init(signer); 25 | 26 | // ** 1st transaction ** 27 | 28 | // This command deploys a Colony and creates a Token and a TokenAuthority contact 29 | // The TokenAuthority can be seen as a "permission-manager" for the token 30 | const [{ colonyAddress, tokenAddress, tokenAuthorityAddress }] = 31 | await colonyNetwork 32 | .createColony( 33 | // Define the token that will be deployed alongside the Colony (ERC20) 34 | { name: 'Test token', symbol: 'TOT' }, 35 | // Make this name unique. The name below is likely to be taken already 36 | 'createcolonytest6', 37 | ) 38 | .tx(); 39 | 40 | if (!colonyAddress || !tokenAddress || !tokenAuthorityAddress) { 41 | return; 42 | } 43 | 44 | console.info('Token address', tokenAddress); 45 | console.info('Colony address', colonyAddress); 46 | console.info('Token authority address', tokenAuthorityAddress); 47 | 48 | // Instantiate colony 49 | const colony = await colonyNetwork.getColony(colonyAddress); 50 | 51 | // ** 2nd and 3rd transaction ** 52 | 53 | // Set the token's authority to the freshly deployed one 54 | if (colony.token instanceof ColonyToken) { 55 | // TODO: MetaTxToken might support multicall in the future 56 | await colony.token.setAuthority(tokenAuthorityAddress).tx(); 57 | await colony.token.setOwner(colony.address).tx(); 58 | } 59 | 60 | // ** 4th transaction ** 61 | 62 | // Install OneTxPayment extension 63 | const [{ extensionId, version }] = await colony 64 | .installExtension(SupportedExtension.oneTx) 65 | .tx(); 66 | if (!extensionId || !version) { 67 | return; 68 | } 69 | console.info('ExtensionId', extensionId); 70 | console.info('Extension version', version); 71 | 72 | // Update colony to see the newly installed extension 73 | await colony.updateExtensions(); 74 | if (!colony.ext.oneTx) { 75 | console.error('Could not instantiate OneTx extension within Colony'); 76 | return; 77 | } 78 | 79 | // ** 5th transaction ** 80 | 81 | // Give Administration and Funding roles to OneTxPayment extension 82 | await colony 83 | .setRoles(colony.ext.oneTx.address, [ 84 | ColonyRole.Administration, 85 | ColonyRole.Funding, 86 | ]) 87 | .tx(); 88 | 89 | console.info('Done :)'); 90 | }; 91 | 92 | start(); 93 | -------------------------------------------------------------------------------- /docs/api/interfaces/ParsedLogTransactionReceipt.md: -------------------------------------------------------------------------------- 1 | # Interface: ParsedLogTransactionReceipt 2 | 3 | Custom Transaction receipt for when we manually have to parse logs (metatransactions) 4 | 5 | ## Hierarchy 6 | 7 | - `TransactionReceipt` 8 | 9 | ↳ **`ParsedLogTransactionReceipt`** 10 | 11 | ## Properties 12 | 13 | ### blockHash 14 | 15 | • **blockHash**: `string` 16 | 17 | #### Inherited from 18 | 19 | TransactionReceipt.blockHash 20 | 21 | ___ 22 | 23 | ### blockNumber 24 | 25 | • **blockNumber**: `number` 26 | 27 | #### Inherited from 28 | 29 | TransactionReceipt.blockNumber 30 | 31 | ___ 32 | 33 | ### byzantium 34 | 35 | • **byzantium**: `boolean` 36 | 37 | #### Inherited from 38 | 39 | TransactionReceipt.byzantium 40 | 41 | ___ 42 | 43 | ### confirmations 44 | 45 | • **confirmations**: `number` 46 | 47 | #### Inherited from 48 | 49 | TransactionReceipt.confirmations 50 | 51 | ___ 52 | 53 | ### contractAddress 54 | 55 | • **contractAddress**: `string` 56 | 57 | #### Inherited from 58 | 59 | TransactionReceipt.contractAddress 60 | 61 | ___ 62 | 63 | ### cumulativeGasUsed 64 | 65 | • **cumulativeGasUsed**: `BigNumber` 66 | 67 | #### Inherited from 68 | 69 | TransactionReceipt.cumulativeGasUsed 70 | 71 | ___ 72 | 73 | ### effectiveGasPrice 74 | 75 | • **effectiveGasPrice**: `BigNumber` 76 | 77 | #### Inherited from 78 | 79 | TransactionReceipt.effectiveGasPrice 80 | 81 | ___ 82 | 83 | ### from 84 | 85 | • **from**: `string` 86 | 87 | #### Inherited from 88 | 89 | TransactionReceipt.from 90 | 91 | ___ 92 | 93 | ### gasUsed 94 | 95 | • **gasUsed**: `BigNumber` 96 | 97 | #### Inherited from 98 | 99 | TransactionReceipt.gasUsed 100 | 101 | ___ 102 | 103 | ### logs 104 | 105 | • **logs**: `Log`[] 106 | 107 | #### Inherited from 108 | 109 | TransactionReceipt.logs 110 | 111 | ___ 112 | 113 | ### logsBloom 114 | 115 | • **logsBloom**: `string` 116 | 117 | #### Inherited from 118 | 119 | TransactionReceipt.logsBloom 120 | 121 | ___ 122 | 123 | ### parsedLogs 124 | 125 | • **parsedLogs**: `LogDescription`[] 126 | 127 | ___ 128 | 129 | ### root 130 | 131 | • `Optional` **root**: `string` 132 | 133 | #### Inherited from 134 | 135 | TransactionReceipt.root 136 | 137 | ___ 138 | 139 | ### status 140 | 141 | • `Optional` **status**: `number` 142 | 143 | #### Inherited from 144 | 145 | TransactionReceipt.status 146 | 147 | ___ 148 | 149 | ### to 150 | 151 | • **to**: `string` 152 | 153 | #### Inherited from 154 | 155 | TransactionReceipt.to 156 | 157 | ___ 158 | 159 | ### transactionHash 160 | 161 | • **transactionHash**: `string` 162 | 163 | #### Inherited from 164 | 165 | TransactionReceipt.transactionHash 166 | 167 | ___ 168 | 169 | ### transactionIndex 170 | 171 | • **transactionIndex**: `number` 172 | 173 | #### Inherited from 174 | 175 | TransactionReceipt.transactionIndex 176 | 177 | ___ 178 | 179 | ### type 180 | 181 | • **type**: `number` 182 | 183 | #### Inherited from 184 | 185 | TransactionReceipt.type 186 | -------------------------------------------------------------------------------- /docs/api/classes/PinataAdapter.md: -------------------------------------------------------------------------------- 1 | # Class: PinataAdapter 2 | 3 | A Colony SDK IPFS adapter for Pinata (https://pinata.cloud) 4 | 5 | In order to use this, sign up for Pinata (if you haven't already) and generate a token. Then either supply this token when instantiating the class (example below) or provide it via the environment variable `COLONY_IPFS_PINATA_TOKEN` (when using NodeJS). Then provide an instance of this class to the [ColonyNetwork](ColonyNetwork.md) or [ColonyEventManager](ColonyEventManager.md) classes (depending on your needs). 6 | 7 | :::danger Tokens are sensitive data 8 | Do not check in your Pinata token into version control and **DO NOT EMBED IT INTO YOUR FRONTEND BUNDLE**. 9 | ::: 10 | 11 | **`Example`** 12 | 13 | ```typescript 14 | import { ColonyNetwork, PinataAdapter } from '@colony/sdk'; 15 | const pinataAdapter = new PinataAdapter('[YOUR_PINANTA_JWT_TOKEN]'); 16 | // Immediately executing async function 17 | (async function() { 18 | const colonyNetwork = ColonyNetwork.init(signerOrProvider, { ipfsAdapter: pinataAdapter }); 19 | })(); 20 | ``` 21 | 22 | ## Implements 23 | 24 | - [`IpfsAdapter`](../interfaces/IpfsAdapter.md) 25 | 26 | ## Constructors 27 | 28 | ### constructor 29 | 30 | • **new PinataAdapter**(`pinataToken?`) 31 | 32 | #### Parameters 33 | 34 | | Name | Type | 35 | | :------ | :------ | 36 | | `pinataToken?` | `string` | 37 | 38 | ## Properties 39 | 40 | ### name 41 | 42 | • **name**: `string` = `'PINATA'` 43 | 44 | Name for the IpfsAdapter. All uppercase please 45 | 46 | #### Implementation of 47 | 48 | [IpfsAdapter](../interfaces/IpfsAdapter.md).[name](../interfaces/IpfsAdapter.md#name) 49 | 50 | ## Methods 51 | 52 | ### getIpfsUrl 53 | 54 | ▸ **getIpfsUrl**(`cid`): `string` 55 | 56 | Should return the whole URL to an IPFS resource on the corresponding gateway (e.g. https://my-ipfs-gateway/ipfs/QmXxxxXXxxXxXxXxxxXXxxxXxXXx). 57 | 58 | #### Parameters 59 | 60 | | Name | Type | Description | 61 | | :------ | :------ | :------ | 62 | | `cid` | `string` | An IPFS hash (CID) | 63 | 64 | #### Returns 65 | 66 | `string` 67 | 68 | The URL to an ipfs resource 69 | 70 | #### Implementation of 71 | 72 | [IpfsAdapter](../interfaces/IpfsAdapter.md).[getIpfsUrl](../interfaces/IpfsAdapter.md#getipfsurl) 73 | 74 | ___ 75 | 76 | ### uploadJson 77 | 78 | ▸ **uploadJson**(`jsonString`): `Promise`<`string`\> 79 | 80 | Function to upload a JSON string to IPFS. Takes the string as an argument (use `JSON.stringify()` if needbe). Returns a promise that resolves to the IPFS hash (CID) 81 | 82 | **`Remarks`** 83 | 84 | This function should ideally **pin** your data on the relevant service. 85 | 86 | #### Parameters 87 | 88 | | Name | Type | Description | 89 | | :------ | :------ | :------ | 90 | | `jsonString` | `string` | JSON string to upload (and pin) to IPFS | 91 | 92 | #### Returns 93 | 94 | `Promise`<`string`\> 95 | 96 | Promise to IPFS hash (CID) 97 | 98 | #### Implementation of 99 | 100 | [IpfsAdapter](../interfaces/IpfsAdapter.md).[uploadJson](../interfaces/IpfsAdapter.md#uploadjson) 101 | -------------------------------------------------------------------------------- /examples/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Colony } from '../src/ColonyNetwork'; 2 | import { ColonyRole, Extension, Id } from '../src/constants'; 3 | import { w } from '../src'; 4 | 5 | // Helper to set up the OneTxPayment extension. This is usually already done for Colonies deployed with the Dapp. 6 | export const setupOneTxPaymentExtension = async (colony: Colony) => { 7 | const colonyClient = colony.getInternalColonyClient(); 8 | let oneTxExtension; 9 | 10 | try { 11 | oneTxExtension = await colonyClient.getExtensionClient( 12 | Extension.OneTxPayment, 13 | ); 14 | } catch (e) { 15 | // Install the OneTxPaymentExtension for the Colony 16 | const installTx = await colonyClient.installExtensionChecked( 17 | Extension.OneTxPayment, 18 | ); 19 | await installTx.wait(); 20 | // Get the address of the deployed extension 21 | oneTxExtension = await colonyClient.getExtensionClient( 22 | Extension.OneTxPayment, 23 | ); 24 | // Set the required roles for the extension 25 | const rolesTx = await colonyClient.setUserRolesWithProofs( 26 | oneTxExtension.address, 27 | Id.RootDomain, 28 | [ColonyRole.Administration, ColonyRole.Funding], 29 | ); 30 | await rolesTx.wait(); 31 | } 32 | }; 33 | 34 | export const setupVotingReputationExtension = async (colony: Colony) => { 35 | const colonyClient = colony.getInternalColonyClient(); 36 | 37 | let votingReputationExtension; 38 | 39 | try { 40 | votingReputationExtension = await colonyClient.getExtensionClient( 41 | Extension.VotingReputation, 42 | ); 43 | } catch (e) { 44 | console.error(e); 45 | // Install the OneTxPaymentExtension for the Colony 46 | const installTx = await colonyClient.installExtensionChecked( 47 | Extension.VotingReputation, 48 | ); 49 | await installTx.wait(); 50 | // Get the address of the deployed extension 51 | votingReputationExtension = await colonyClient.getExtensionClient( 52 | Extension.VotingReputation, 53 | ); 54 | // Set the required roles for the extension 55 | // Note: none of the roles here are really _required_. For Motions only the permissions that are needed for functionality that requires it and you want to support with Motions 56 | const rolesTx = await colonyClient.setUserRolesWithProofs( 57 | votingReputationExtension.address, 58 | Id.RootDomain, 59 | [ 60 | ColonyRole.Recovery, 61 | ColonyRole.Root, 62 | ColonyRole.Arbitration, 63 | ColonyRole.Architecture, 64 | ColonyRole.Funding, 65 | ColonyRole.Administration, 66 | ], 67 | ); 68 | await rolesTx.wait(); 69 | 70 | // For an explanation of all the parameters see here https://colony.gitbook.io/colony-network/interfaces/votingreputation#initialise-uint256-_totalstakefraction-uint256-_voterrewardfraction-uint256-_userminstakefraction-ui and here https://colony.gitbook.io/colony/extensions/governance/parameters 71 | const initTx = await votingReputationExtension.initialise( 72 | w`0.01`, 73 | w`0.2`, 74 | w`0.01`, 75 | w`0.7`, 76 | 6 * 60, 77 | 6 * 60, 78 | 6 * 60, 79 | 6 * 60, 80 | ); 81 | 82 | await initTx.wait(); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@colony/sdk", 3 | "version": "1.6.0", 4 | "license": "GPL-3.0-only", 5 | "dependencies": { 6 | "@colony/colony-event-metadata-parser": "^2.0.0", 7 | "@colony/colony-js": "^6.3.5", 8 | "@urql/core": "^2.5.0", 9 | "cross-fetch": "^3.1.5", 10 | "fetch-retry": "^5.0.2", 11 | "graphql": "^16.5.0", 12 | "isomorphic-ws": "^4.0.1", 13 | "subscriptions-transport-ws": "^0.11.0" 14 | }, 15 | "devDependencies": { 16 | "@colony/eslint-config-colony": "^9.0.2", 17 | "@picocss/pico": "^1.5.7", 18 | "@types/inquirer": "^8.2.1", 19 | "@types/ws": "^8.5.3", 20 | "@types/yargs": "^17.0.10", 21 | "@typescript-eslint/eslint-plugin": "^5.36.2", 22 | "@typescript-eslint/parser": "^5.36.2", 23 | "esbuild": "^0.14.27", 24 | "eslint": "^8.12.0", 25 | "eslint-config-airbnb-base": "^15.0.0", 26 | "eslint-config-prettier": "^8.5.0", 27 | "eslint-plugin-eslint-comments": "^3.2.0", 28 | "eslint-plugin-import": "^2.25.4", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "eslint-plugin-tsdoc": "^0.2.17", 31 | "ethers": "^5.7.1", 32 | "fast-glob": "^3.2.11", 33 | "husky": "^7.0.4", 34 | "inquirer": "^8.2.4", 35 | "lint-staged": "^12.4.0", 36 | "prettier": "^2.6.1", 37 | "release-it": "^15.4.1", 38 | "rimraf": "^3.0.2", 39 | "ts-node": "^10.7.0", 40 | "typedoc": "^0.23.13", 41 | "typedoc-plugin-markdown": "^3.13.5", 42 | "typescript": "^4.7.4", 43 | "wonka": "^4.0.15", 44 | "yargs": "^17.5.1" 45 | }, 46 | "peerDependencies": { 47 | "ethers": "^5.6.1" 48 | }, 49 | "scripts": { 50 | "examples:node": "ts-node examples/node/index.ts", 51 | "examples:browser": "esbuild --bundle examples/browser/src/*.ts --servedir=examples/browser/web", 52 | "build": "npm run clean && npm run compile-cjs && npm run compile-esm && npm run compile-types && npm run build-docs", 53 | "build-docs": "typedoc", 54 | "build-examples": "esbuild --bundle examples/browser/src/*.ts --outdir=examples/browser/web --minify", 55 | "clean": "rimraf ./dist", 56 | "compile-cjs": "tsc -p tsconfig.build.json --module commonjs --outDir dist/cjs", 57 | "compile-esm": "tsc -p tsconfig.build.json --module es2015 --outDir dist/esm", 58 | "compile-types": "tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist/types", 59 | "lint": "eslint --ext .ts src examples", 60 | "no-git-changes": "./scripts/no-git-changes.sh", 61 | "prepare": "husky install", 62 | "release": "npm test && npm run build && release-it", 63 | "test": "npm run lint && npm run typecheck", 64 | "typecheck": "tsc --noEmit" 65 | }, 66 | "engines": { 67 | "node": "^16.0.0" 68 | }, 69 | "exports": { 70 | ".": { 71 | "import": "./dist/esm/index.js", 72 | "require": "./dist/cjs/index.js" 73 | }, 74 | "./graph": { 75 | "import": "./dist/esm/graph/index.js", 76 | "require": "./dist/cjs/graph/index.js" 77 | } 78 | }, 79 | "typesVersions": { 80 | "*": { 81 | "*": [ 82 | "dist/types/index.d.ts" 83 | ], 84 | "graph": [ 85 | "dist/types/graph/index.d.ts" 86 | ] 87 | } 88 | }, 89 | "files": [ 90 | "dist", 91 | "LICENSE", 92 | "README.md" 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /examples/browser/src/events.ts: -------------------------------------------------------------------------------- 1 | import { providers, utils } from 'ethers'; 2 | 3 | import { 4 | ColonyEventManager, 5 | ColonyRpcEndpoint, 6 | MetadataType, 7 | } from '../../../src'; 8 | import type { ColonyEvent } from '../../../src'; 9 | 10 | const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.Gnosis); 11 | const { isAddress } = utils; 12 | 13 | // This event listener will only list for the `DomainAdded` event in the Colony of the user's choice. Run this and then create a Team in that Colony, to see it being picked up here 14 | const setupEventListener = ( 15 | colonyAddress: string, 16 | callback: (events: ColonyEvent[]) => void, 17 | ) => { 18 | const manager = new ColonyEventManager(provider); 19 | 20 | const domainEvents = manager.createMultiFilter( 21 | manager.eventSources.Colony, 22 | ['DomainAdded(address,uint256)', 'DomainMetadata(address,uint256,string)'], 23 | colonyAddress, 24 | ); 25 | 26 | let i = 0; 27 | 28 | manager.provider.on('block', async (no) => { 29 | i += 1; 30 | // Only get events every 5 blocks to debounce this a little bit 31 | if (i === 4) { 32 | const events = await manager.getMultiEvents([domainEvents], { 33 | fromBlock: no - i, 34 | toBlock: no, 35 | }); 36 | if (events.length) callback(events); 37 | i = 0; 38 | } 39 | }); 40 | }; 41 | 42 | // Just some basic setup to display the UI 43 | const addressInput: HTMLInputElement | null = 44 | document.querySelector('#address'); 45 | const button = document.querySelector('#button'); 46 | const errElm: HTMLParagraphElement | null = document.querySelector('#error'); 47 | const resultElm: HTMLParagraphElement | null = 48 | document.querySelector('#result'); 49 | 50 | if (!addressInput || !button || !errElm || !resultElm) { 51 | throw new Error('Could not find all required HTML elements'); 52 | } 53 | 54 | const panik = (err: string) => { 55 | errElm.innerText = err; 56 | }; 57 | const kalm = () => { 58 | errElm.innerText = ''; 59 | }; 60 | const speak = (msg: string) => { 61 | resultElm.innerText = msg; 62 | }; 63 | 64 | button.addEventListener('click', async () => { 65 | kalm(); 66 | const colonyAddress = addressInput.value; 67 | if (!isAddress(colonyAddress)) { 68 | return panik('This is not a valid address'); 69 | } 70 | addressInput.value = ''; 71 | setupEventListener(colonyAddress, (events) => { 72 | speak( 73 | `A domain with id ${events[0].data.domainId} was created on Colony ${events[0].address}.`, 74 | ); 75 | events.forEach(async (event) => { 76 | if (event.getMetadata) { 77 | const metadata = await event.getMetadata(); 78 | // TODO: improve this on the API level (maybe .isMetadataType(MetadataType.Domain)) 79 | if ( 80 | metadata && 81 | typeof metadata != 'string' && 82 | 'domainName' in metadata 83 | ) { 84 | speak( 85 | `A domain with id ${event.data.domainId} was created on Colony ${event.address}. It's name is ${metadata.domainName}, it's color ${metadata.domainColor} and was created for the following purpose: ${metadata.domainPurpose}`, 86 | ); 87 | } 88 | } 89 | }); 90 | }); 91 | speak(`Set up event listener for Colony ${colonyAddress}`); 92 | return null; 93 | }); 94 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [developers@colony.io](mailto:developers@colony.io). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: https://contributor-covenant.org 46 | [version]: https://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /docs/api/interfaces/ColonyEvent.md: -------------------------------------------------------------------------------- 1 | # Interface: ColonyEvent 2 | 3 | An Event that came from a contract within the Colony Network 4 | 5 | ## Type parameters 6 | 7 | | Name | Type | 8 | | :------ | :------ | 9 | | `T` | extends [`MetadataType`](../enums/MetadataType.md) | 10 | 11 | ## Hierarchy 12 | 13 | - [`ColonyFilter`](ColonyFilter.md) 14 | 15 | ↳ **`ColonyEvent`** 16 | 17 | ## Properties 18 | 19 | ### address 20 | 21 | • `Optional` **address**: `string` \| `string`[] 22 | 23 | #### Inherited from 24 | 25 | [ColonyFilter](ColonyFilter.md).[address](ColonyFilter.md#address) 26 | 27 | ___ 28 | 29 | ### data 30 | 31 | • **data**: `Result` 32 | 33 | ___ 34 | 35 | ### eventName 36 | 37 | • **eventName**: `string` 38 | 39 | The full event signature of this event (e.g. `TokenMinted(uint256))` 40 | 41 | #### Inherited from 42 | 43 | [ColonyFilter](ColonyFilter.md).[eventName](ColonyFilter.md#eventname) 44 | 45 | ___ 46 | 47 | ### eventSource 48 | 49 | • **eventSource**: keyof [`EventSources`](EventSources.md) 50 | 51 | The Colony contract the event originated from 52 | 53 | #### Inherited from 54 | 55 | [ColonyFilter](ColonyFilter.md).[eventSource](ColonyFilter.md#eventsource) 56 | 57 | ___ 58 | 59 | ### fromBlock 60 | 61 | • `Optional` **fromBlock**: `BlockTag` 62 | 63 | #### Inherited from 64 | 65 | [ColonyFilter](ColonyFilter.md).[fromBlock](ColonyFilter.md#fromblock) 66 | 67 | ___ 68 | 69 | ### getMetadata 70 | 71 | • `Optional` **getMetadata**: () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`T`]\>\> 72 | 73 | #### Type declaration 74 | 75 | ▸ (): `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`T`]\>\> 76 | 77 | ##### Returns 78 | 79 | `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`T`]\>\> 80 | 81 | ___ 82 | 83 | ### toBlock 84 | 85 | • `Optional` **toBlock**: `BlockTag` 86 | 87 | #### Inherited from 88 | 89 | [ColonyFilter](ColonyFilter.md).[toBlock](ColonyFilter.md#toblock) 90 | 91 | ___ 92 | 93 | ### topics 94 | 95 | • `Optional` **topics**: (``null`` \| `string` \| `string`[])[] 96 | 97 | #### Inherited from 98 | 99 | [ColonyFilter](ColonyFilter.md).[topics](ColonyFilter.md#topics) 100 | 101 | ___ 102 | 103 | ### transactionHash 104 | 105 | • **transactionHash**: `string` 106 | -------------------------------------------------------------------------------- /docs/api/classes/TxCreator.md: -------------------------------------------------------------------------------- 1 | # Class: TxCreator 2 | 3 | An umbrella API for all kinds of transactions 4 | 5 | The `TxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network. This is the base class of the TxCreator that only supports the `force()` action and no metatransactions. 6 | 7 | ## Create a standard transaction ("force" in dApp) 8 | 9 | - [TxCreator.tx](TxCreator.md#tx): force a Colony transaction, knowing you have the permissions to do so 10 | 11 | Learn more about these functions in their individual documentation 12 | 13 | ## Type parameters 14 | 15 | | Name | Type | 16 | | :------ | :------ | 17 | | `C` | extends [`BaseContract`](../interfaces/BaseContract.md) | 18 | | `M` | extends keyof `C`[``"functions"``] | 19 | | `E` | extends `EventData` | 20 | | `MD` | extends [`MetadataType`](../enums/MetadataType.md) | 21 | 22 | ## Hierarchy 23 | 24 | - **`TxCreator`** 25 | 26 | ↳ [`MetaTxCreator`](MetaTxCreator.md) 27 | 28 | ## Constructors 29 | 30 | ### constructor 31 | 32 | • **new TxCreator**<`C`, `M`, `E`, `MD`\>(`__namedParameters`) 33 | 34 | #### Type parameters 35 | 36 | | Name | Type | 37 | | :------ | :------ | 38 | | `C` | extends [`BaseContract`](../interfaces/BaseContract.md) | 39 | | `M` | extends `string` \| `number` \| `symbol` | 40 | | `E` | extends `EventData` | 41 | | `MD` | extends [`MetadataType`](../enums/MetadataType.md) | 42 | 43 | #### Parameters 44 | 45 | | Name | Type | 46 | | :------ | :------ | 47 | | `__namedParameters` | `Object` | 48 | | `__namedParameters.args` | `unknown`[] \| () => `Promise`<`unknown`[]\> | 49 | | `__namedParameters.colonyNetwork` | [`ColonyNetwork`](ColonyNetwork.md) | 50 | | `__namedParameters.contract` | `C` | 51 | | `__namedParameters.eventData?` | (`receipt`: `ContractReceipt`) => `Promise`<`E`\> | 52 | | `__namedParameters.metadataType?` | `MD` | 53 | | `__namedParameters.method` | `M` | 54 | | `__namedParameters.txConfig?` | [`TxConfig`](../interfaces/TxConfig.md)<`MD`\> | 55 | 56 | ## Methods 57 | 58 | ### tx 59 | 60 | ▸ **tx**(): `Promise`<[`E`, `ContractReceipt`, () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](../interfaces/ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](../interfaces/DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](../interfaces/DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`MD`]\>\>] \| [`E`, `ContractReceipt`]\> 61 | 62 | Create a standard transaction ("force" in dApp) 63 | 64 | **`Remarks`** 65 | 66 | The user sending this transaction has to have the appropriate permissions to do so. Learn more about permissions in Colony [here](/develop/dev-learning/permissions). 67 | 68 | #### Returns 69 | 70 | `Promise`<[`E`, `ContractReceipt`, () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](../interfaces/ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](../interfaces/DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](../interfaces/DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`MD`]\>\>] \| [`E`, `ContractReceipt`]\> 71 | 72 | A tupel of event data and contract receipt (and a function to retrieve metadata if applicable) 73 | -------------------------------------------------------------------------------- /examples/browser/web/local-reputation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - local deployment - make a payment in the MetaColony and check the user's reputation 11 |

12 |

First make sure that the Colony contracts are deployed locally and the Reputation Oracle is set 15 | up.

16 |

We'll be using wallet address 0xb77D57F4959eAfA0339424b83FcFaf9c15407461 to connect to the locally deployed 17 | MetaColony. This address was used to deploy the MetaColony locally and is the "owner" of it. Any transaction 18 | will automatically be signed as we have the private key hardcoded. No connection to MetaMask is needed.

19 |

1) Let's connect to the ColonyNetwork first Paste in the address from the 20 | `etherrouter-address.json` in the `colonyNetwork` directory

21 | 22 | 23 |

2) Then, we mint some CLNY and fund the MetaColony with it (make it at least 24 | 10!)

25 | 26 | 27 |

3) Next, we pay someone for a task in the MetaColony's root team (in the MetaColony's native token CLNY - 28 | that's important!)

29 | 30 | 31 |

4) Jump forward in time - Reputation is awarded in cycles. In our case it's set to 1 hour. As we have full 32 | control over our local blockchain, Ganache allows us to travel to the future! Fasten your seatbelts, here we

33 |

Instructions: 34 |

    35 |
  • i) Press the button
  • 36 |
  • ii) Wait for the message
    ✅ New reputation hash confirmed
    to appear in your console (reputation 37 | oracle)
  • 38 |
  • iii) Press the button again
  • 39 |
  • iv) Wait for the above message again
  • 40 |
  • v) Then go to the next step
  • 41 |
42 | 43 |

Note: We're using Ganache's evm_increaseTime and evm_mine methods to first increase the block time artificially by one hour and then force a 48 | block to mine. This will trigger the local reputation oracle/miner to award the pending reputation. We have to do 49 | that twice, if the reputation oracle was just started for the first time.

50 |

5) Ask the oracle for the user's reputation

51 | 52 |
53 |
54 | Message 55 |
56 |

57 |

58 |
59 |
60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/browser/web/motions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - Motion workflow

11 |

Using wallet address

12 |

1) Let's connect to the Colony first For testing, it is beneficial to set the period 13 | length of the motion phases to very low values (e.g. 2 minutes)

14 | 15 | 16 |

2) Now we approve some of the CLNY of the above wallet for the Colony and the VotingReputation 17 | staking, 18 | which will remove it from the user's wallet (you only need to do this once for this example)

19 | 20 | 21 |

3) Then, we create a motion to pay some of the native token to Carol: 22 | 0x42b9f8673a9187952a5dF24132eEa04268a14BCC (this will also incur reputation if the motion passes) (must be less than the amount that is in the Colony's root team!) 24 |

25 | 26 | 27 |

4) Next, we look at the motion more closely

28 | 29 | 30 |

You will see the domainId in which the motion was created, the altTarget (which is the OneTxPayment extension in 31 | this case, as we want to execute the `pay` function on it), the encoded action and the stakes remaining for 32 | activation.

33 |

5) Okay, now we're ready to stake. Feel free to stake any amount, then get the motion again and see how it 34 | goes up :)

35 | 36 | 37 | 38 |

6) We can also simulate voting. For that, we activate both sides by staking the required amount on both 39 | sides from a single address. That usually won't happen in the wild (except if someone wants to force voting for 40 | some 41 | reason). Stake on both sides and then get the motion data to see if remainingYayStakes and 42 | remainingNayStakes are both 0.0 43 |

44 | 45 | 46 |

7) Reveal the vote After the voting phase is over the votes can be revealed. Only if a vote is revealed it 47 | is counted and the voting reward is paid out

48 | 49 |

8) If the motion was successful, we can finalize it. In our example we can only finalize it if we wait 50 | long 51 | enough after staking was only activated for Yay or if we voted for Yay.

52 | 53 |
54 |
55 | Message 56 |
57 |

58 |

59 |
60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/ColonyNetwork/ERC2612Token.ts: -------------------------------------------------------------------------------- 1 | import type { ApprovalEventObject } from '@colony/colony-js/events'; 2 | 3 | import { 4 | ERC2612Token as ERC2612TokenType, 5 | ERC2612TokenFactory, 6 | } from '@colony/colony-js/tokens'; 7 | import { BigNumberish } from 'ethers'; 8 | 9 | import { extractEvent } from '../utils'; 10 | import { ColonyNetwork } from './ColonyNetwork'; 11 | import { ERC20Token } from './ERC20Token'; 12 | 13 | export class ERC2612Token extends ERC20Token { 14 | protected tokenClient: ERC2612TokenType; 15 | 16 | /** 17 | * Creates a new instance of an ERC2612 token (ERC20 with Permit extension) 18 | * 19 | * @remarks This does not deploy a new token, only connects to an exisiting one 20 | * 21 | * @param colonyNetwork - A [[ColonyNetwork]] instance 22 | * @param token - A token address or a full contract (like on a colony token client) 23 | * @returns An ERC2612 token abstraction instance 24 | */ 25 | constructor(colonyNetwork: ColonyNetwork, token: ERC2612TokenType | string) { 26 | super(colonyNetwork, token); 27 | if (typeof token == 'string') { 28 | this.tokenClient = ERC2612TokenFactory.connect( 29 | token, 30 | colonyNetwork.signerOrProvider, 31 | ); 32 | } else { 33 | this.tokenClient = token; 34 | } 35 | this.address = this.tokenClient.address; 36 | this.colonyNetwork = colonyNetwork; 37 | } 38 | 39 | /** 40 | * Provide direct access to the internally used ColonyJS TokenClient client. Only use when you know what you're doing 41 | * @internal 42 | * 43 | * @returns The internally used TokenClient 44 | */ 45 | getInternalTokenClient(): ERC2612TokenType { 46 | return this.tokenClient; 47 | } 48 | 49 | /** 50 | * Permit `amount` of the wallet owners holdings of the specified token. 51 | * 52 | * This is the same as [[ERC20Token.approve]] but works only for gasless metatransactions. If you have a Colony-deployed token, use `approve`. This is mainly to support gasless transactions for BYOT (bring-your-own-token). 53 | * 54 | * This follows the EIP-2612 "Permit" specification. See https://eips.ethereum.org/EIPS/eip-2612. 55 | * 56 | * In order for the wallet owner to stake tokens, that amount has to be approved and deposited into the Colony first. In the dapp the process is called "Activation" of a certain amount of the Colony's native token. The wallet must hold at least the amount of the token that will be approved. 57 | * 58 | * @remarks Note that the arguments are turned around when comparing with the EIP2612 format. 59 | * 60 | * @example 61 | * ```typescript 62 | * import { w } from '@colony/sdk'; 63 | * 64 | * // Immediately executing async function 65 | * (async function() { 66 | * // Permit 100 tokens to be "activated" 67 | * await colony.token.permit(w`100`).metaTx(); 68 | * // Deposit the tokens 69 | * await colonyNetwork.locking.deposit(token.address, w`100`).force(); 70 | * })(); 71 | * ``` 72 | * 73 | * @param amount - Amount of the token to be approved 74 | * @param spender - Spender to approve the amount for. Defaults to the Colony Network 75 | * 76 | * @returns A transaction creator 77 | * 78 | * #### Event data 79 | * 80 | * | Property | Type | Description | 81 | * | :------ | :------ | :------ | 82 | * | `src` | string | The address that approved the tokens from their wallet | 83 | * | `guy` | string | Address of the TokenLocking contract | 84 | * | `wad` | BigNumber | Amount that was approved | 85 | */ 86 | permit(amount: BigNumberish, spender?: string) { 87 | const spenderArg = spender || this.colonyNetwork.locking.address; 88 | return this.colonyNetwork.createEip2612TxCreator( 89 | this.tokenClient, 90 | 'permit', 91 | [spenderArg, amount], 92 | async (receipt) => ({ 93 | ...extractEvent('Approval', receipt), 94 | }), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/graph/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createClient, 3 | defaultExchanges, 4 | subscriptionExchange, 5 | } from '@urql/core'; 6 | import fetch from 'cross-fetch'; 7 | import WebSocket from 'isomorphic-ws'; 8 | import { SubscriptionClient } from 'subscriptions-transport-ws'; 9 | 10 | const DEFAULT_ENDPOINT_HTTP = 11 | 'https://xdai.colony.io/graph/subgraphs/name/joinColony/subgraph'; 12 | const DEFAULT_ENDPOINT_WS = 13 | 'wss://xdai.colony.io/graph/subgraphs/name/joinColony/subgraph'; 14 | 15 | export interface SubgraphClientOptions { 16 | /** The GraphQL endpoint for HTTP connections */ 17 | endpointHttp?: string; 18 | /** The GraphQL endpoint for Websocket connections */ 19 | endpointWs?: string; 20 | } 21 | 22 | // TODO: (v2) remove this file from package.json as separate module 23 | 24 | /** 25 | * Creates a Colony Subgraph client 26 | * 27 | * The Colony Subgraph client is nothing else than a ready-to-use [`urql`](https://formidable.com/open-source/urql/) client connected to the Colony Subgraph with subscriptions enabled. Please refer to the following references if you'd like to know more about [The Graph](https://thegraph.com/) or [GraphQL](https://graphql.org/) in general. 28 | * 29 | * The Colony Subgraph's schema and resolvers are kept up-to-date by the Colony team and can be explored here: [Colony Subgraph](https://thegraph.com/hosted-service/subgraph/joincolony/colony-xdai). There you can make test queries to the Colony Subgraph and explore it all the way down the rabbit hole :) 30 | * 31 | * @param options - Define configuration options to instantiate the client with 32 | * @returns A ready-to-use `urql` GraphQL client instance 33 | * 34 | * @example 35 | * Retrieve the 10 most recent "DomainAdded" events across all Colonies 36 | * ```typescript 37 | * import { createSubgraphClient, gql } from '@colony/sdk/graph'; 38 | * 39 | * const colonySubgraph = createSubgraphClient(); 40 | * 41 | * const QUERY = gql` 42 | * query DomainAddedEvents { 43 | * events( 44 | * first: 10 45 | * orderBy: timestamp 46 | * orderDirection: desc 47 | * where: { name_contains: "DomainAdded" } 48 | * ) { 49 | * id 50 | * address 51 | * associatedColony { 52 | * colonyAddress: id 53 | * } 54 | * name 55 | * args 56 | * timestamp 57 | * } 58 | * } 59 | * `; 60 | * 61 | * colonySubgraph 62 | * .query(QUERY) 63 | * .toPromise() 64 | * .then((result) => { 65 | * console.info(result.data.events[0]); 66 | * }); 67 | * ``` 68 | */ 69 | export const createSubgraphClient = (options?: SubgraphClientOptions) => { 70 | // Create a websocket client connecting to the Colony ws subgraph 71 | const subscriptionClient = new SubscriptionClient( 72 | options?.endpointWs || DEFAULT_ENDPOINT_WS, 73 | { 74 | reconnect: true, 75 | }, 76 | WebSocket, 77 | ); 78 | 79 | return createClient({ 80 | fetch, 81 | url: options?.endpointHttp || DEFAULT_ENDPOINT_HTTP, 82 | exchanges: [ 83 | ...defaultExchanges, 84 | subscriptionExchange({ 85 | forwardSubscription: (operation) => 86 | subscriptionClient.request(operation), 87 | }), 88 | ], 89 | }); 90 | }; 91 | 92 | // TODO: add createSubgraphClientWithSubscription (or something like that) which is basically the above and remove the subscription stuff from the above 93 | 94 | /** 95 | * The `gql` interpolation function 96 | * 97 | * This is just the passed-down [`gql`](https://formidable.com/open-source/urql/docs/api/core/#gql) function from `urlq`. This function is used to parse GraphQL queries as strings and create query objects the GraphQL client can process. Please check out it's documentation to get to know more. 98 | */ 99 | export { gql } from '@urql/core'; 100 | 101 | export { default as ColonyGraph, GraphDomain } from './ColonyGraph'; 102 | -------------------------------------------------------------------------------- /docs/guides/transactions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: A guide on how to create transactions within Colony. You can create motions and even gasless MetaTransactions in a very straightforward and concise way. 3 | 4 | sidebar_position: 1 5 | --- 6 | 7 | # How to create transactions 8 | 9 | Within Colony, there are a few ways to do an action. As a colony is a permissioned contract, not everyone can just do anything they like, users (or contracts) have to have the right permission in the relevant team to do so. 10 | If a governance extension is installed for the colony, this behaviour changes. Using [Motions & Disputes](../api/classes/VotingReputation.md) for example, it is possible to propose an action without having the appropriate permissions. 11 | As permissioned functions and governance functions take the same arguments, we have unified this in one API, that could be extended in the future. 12 | 13 | ## Creating transactions and motions 14 | 15 | So what does this mean? Let's look at an example. We would like to create a team using Colony SDK. If we have the right permissions, we can just do: 16 | 17 | ```typescript 18 | // Immediately executing async function 19 | (async function() { 20 | // Create a new team (domain) within our colony (using sheer force ;) ) 21 | const [{ domainId }] = await colony.createTeam().tx(); 22 | })(); 23 | ``` 24 | 25 | **Note the `tx()` at the end.** That's where we tell Colony SDK to create a transaction that will take its action immediately, given we have the right permissions. 26 | 27 | If we wanted to create a motion instead (see [VotingReputation](../api/classes/VotingReputation.md)) to create a new team, we'd replace `tx()` with `motion(motionTeam)`: 28 | 29 | ```typescript 30 | import { Id } from '@colony/sdk'; 31 | // Immediately executing async function 32 | (async function() { 33 | // Create a motion in the Root team to create a new team. Will have to go through the whole motion workflow 34 | const [{ motionId }] = await colony.createTeam().motion(Id.RootDomain); 35 | })(); 36 | ``` 37 | 38 | Note that you have to supply a `motionTeam` when creating a motion. This is the id of the team in which the motion will be created. This will have an effect on who will be able to object or vote and with how much reputation. 39 | 40 | **If the `motionTeam` is not specified it will default to the Root domain**! 41 | 42 | 43 | ## Creating gasless transactions and motions (MetaTransactions) 44 | 45 | Colony SDK supports another way of sending off transactions or motions which we call *MetaTransactions*. These are gasless transactions (which makes them entirely free for the user) and are signed by the user who wants to issue them and send off by a Colony server. To send a MetaTransaction, just use `metaTx()` instead of `tx()` and `metaMotion()` instead of `motion()`. The wallet then needs to sign a message instead of a transaction, which will be transferred to the Colony MetaTransaction broadcaster. The broadcaster will send back a transaction id from which the receipt and event data will be retrieved as usual. 46 | 47 | Here's an example on how to file a motion through a metatransaction: 48 | 49 | ```typescript 50 | import { Id } from '@colony/sdk'; 51 | // Immediately executing async function 52 | (async function() { 53 | // Create a motion in the Root team to create a new team using a metatransaction 54 | const [{ motionId }] = await colony.createTeam().metaMotion(); 55 | })(); 56 | ``` 57 | 58 | ## tl;dr 59 | 60 | Okay, what did we learn? Here's a little overview: 61 | 62 | ### Create an immediate action 63 | 64 | - [TxCreator.tx](../api/classes/TxCreator.md#tx): create ("force") a Colony transaction, knowing you have the permissions to do so 65 | - [TxCreator.metaTx](../api/classes/TxCreator.md#metatx): same as `tx()`, but send as a gasless metatransaction 66 | 67 | ### Create a motion to trigger an action once it passes 68 | 69 | - [TxCreator.motion](../api/classes/TxCreator.md#motion): create a motion (needs the motion's domain as a parameter) 70 | - [TxCreator.metaMotion](../api/classes/TxCreator.md#metamotion): same as `motion()`, but sends a gasless metatransaction 71 | 72 | Also refer to the [TxCreator](../api/classes/TxCreator.md) documentation if you'd like to learn more. 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MOVED. Colony SDK lives [here](https://github.com/JoinColony/colonyJS/tree/main/packages/sdk) now. 2 | 3 | 🚀 The Colony SDK. Get started with Colony quickly 4 | 5 |
6 | 7 |
8 | 9 | [![Discord](https://img.shields.io/discord/562263648173555742)](https://discord.gg/feVZWwysqM) 10 | 11 | # Colony SDK 12 | 13 | The Colony SDK is under heavy development by the community and will be an easy-to-use interface for the Colony Network contracts, providing simple functions that hide the dark magic going on under the hood of ColonyJS. 14 | It covers _everything_ the dApp can do, so you'll be able to run your DAO entirely programmatically 👩‍💻 15 | 16 | Colony SDK also includes a variety of examples to get you up and building with Colony in no time! 17 | 18 | ## Quickstart 19 | 20 | ```javascript 21 | import { providers } from 'ethers'; 22 | import { ColonyNetwork, toEth } from '@colony/sdk'; 23 | 24 | // If MetaMask is installed there will be an `ethereum` object on the `window` 25 | // NOTE: Make sure MetaMask is connected to Gnosis chain (see https://www.xdaichain.com/for-users/wallets/metamask/metamask-setup) 26 | const provider = new providers.Web3Provider(window.ethereum); 27 | 28 | // Get the Colony's XDAI funding in the ROOT pot (id 1) 29 | const start = async () => { 30 | // This will try to connect the page to MetaMask 31 | await provider.send('eth_requestAccounts', []); 32 | // Create a new connection to the Colony Network contracts using the MetaMask "wallet" 33 | const colonyNetwork = await ColonyNetwork.init(provider.getSigner()); 34 | // Connect to the MetaColony (this could be replaced with your own colony using `colonyNetwork.getColony(COLONY_ADDRESS)`) 35 | const metaColony = await colonyNetwork.getMetaColony(); 36 | // Get the CLNY funding for the MetaColony (CLNY is it's native token) 37 | const funding = await metaColony.getBalance(); 38 | // The funding will be in wei (x * 10^18), so we format into a readable string using the `toEth` function 39 | alert('MetaColony balance is ' + toEth(funding) + ' CLNY'); 40 | }; 41 | 42 | start(); 43 | ``` 44 | 45 | ## Documentation 46 | 47 | [🖺 Click here for docs!](https://docs.colony.io/colonysdk) 48 | 49 | ## Running the examples 50 | 51 | First, clone this repo: 52 | ```bash 53 | git clone https://github.com/JoinColony/colonySDK.git 54 | ``` 55 | 56 | Then install all the required dependencies (this will install [ethers.js](https://docs.ethers.io/v5/) and [colonyJS](https://github.com/JoinColony/colonyJS) as well as some required development dependencies): 57 | 58 | ```bash 59 | npm install 60 | ``` 61 | 62 | Then you can run the examples: 63 | 64 | ### Node.js 65 | 66 | ```bash 67 | npm run examples:node 68 | ``` 69 | 70 | ### Browser (vanilla JS example) 71 | 72 | ```bash 73 | npm run examples:browser 74 | ``` 75 | 76 | ### Some notes 77 | 78 | These examples will run on Gnosis chain. If you'd like to make transactions, you will need some XDAI. Reach out to us in our [Discord](https://discord.gg/feVZWwysqM) if you're having trouble starting out. 79 | 80 | ## Development 81 | 82 | ### Prerequisites 83 | 84 | - Node `>=16.0.0` 85 | 86 | _You may find it helpful to use [Node Version Manager (`nvm`)](https://github.com/nvm-sh/nvm) to manage Node versions._ 87 | 88 | ### Creating a new release 89 | 90 | colonySDK is using [`release-it`](https://github.com/release-it/release-it) to create new releases. To create and publish a new release, commit your changes, then execute 91 | 92 | ```bash 93 | npm run release -- SEMVER_TAG # SEMVER_TAG is major, minor, patch 94 | ``` 95 | 96 | If you don't supply a `GITHUB_TOKEN` environment variable, `release-it` will open a browser window and pre-populate the corresponding release input fields for you. 97 | 98 | **Frequent commits and descriptive commit messages** will help when `release-it` tries to autogenerate the changelog. 99 | 100 | ### Contribute 101 | 102 | _Are you interested in contributing?_ Check out the following document for more information: 103 | 104 | - [Contributing](CONTRIBUTING.md) 105 | -------------------------------------------------------------------------------- /docs/getting-started/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: A stupidly short guide to get started with Colony development 4 | --- 5 | 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | 9 | # Start Building 10 | 11 | ## Prerequisites 12 | 13 | * NodeJS v 16.15.0 is installed on your system (use [`nvm`](https://github.com/nvm-sh/nvm) for changing NodeJS versions on the fly) 14 | * A JavaScript/TypeScript project with a `package.json` and the `@colony/sdk` and `ethers` packages installed. 15 | 16 | :::tip 17 | To get started even faster, use our [**`Colony Starter`**](https://github.com/JoinColony/colonyStarter) template. It's just a matter of cloning the repository and running `npm run serve` to get to the first working example. Follow the guide after the link for more information. 18 | ::: 19 | 20 | ## Installation 21 | 22 | It's as easy as 23 | 24 | ```bash 25 | npm install ethers @colony/sdk 26 | ``` 27 | 28 | You'll need `ethers` v5.x as a dependency in your project. 29 | 30 | ## Connecting to Colony on Gnosis Chain 31 | 32 | 33 | 34 | 35 | :::info 36 | For browser based projects, consider using a build system like [esbuild](https://esbuild.github.io/) that can understand the `import` commands and will bundle all necessary libraries into one file. Again, feel free to just use the [colonyStarter](https://github.com/JoinColony/colonyStarter) template which has this already set up for you. 37 | ::: 38 | 39 | ```javascript 40 | import { providers } from 'ethers'; 41 | import { ColonyNetwork, toEth } from '@colony/sdk'; 42 | 43 | // If MetaMask is installed there will be an `ethereum` object on the `window` 44 | // NOTE: Make sure MetaMask is connected to Gnosis chain (see https://docs.gnosischain.com/tools/wallets/metamask) 45 | const provider = new providers.Web3Provider(window.ethereum); 46 | 47 | // Get the Colony's XDAI funding in the ROOT pot (id 1) 48 | const start = async () => { 49 | // This will try to connect the page to MetaMask 50 | await provider.send('eth_requestAccounts', []); 51 | // Create a new connection to the Colony Network contracts using the MetaMask "wallet" 52 | const colonyNetwork = await ColonyNetwork.init(provider.getSigner()); 53 | // Connect to the MetaColony (this could be replaced with your own colony using `colonyNetwork.getColony(COLONY_ADDRESS)`) 54 | const metaColony = await colonyNetwork.getMetaColony(); 55 | // Get the CLNY funding for the MetaColony (CLNY is it's native token) 56 | const funding = await metaColony.getBalance(); 57 | // The funding will be in wei (x * 10^18), so we format into a readable string using the `toEth` function 58 | alert('MetaColony balance is ' + toEth(funding) + ' CLNY'); 59 | }; 60 | 61 | start(); 62 | ``` 63 | 64 | Include the resulting bundle in an HTML file and open it in you favorite browser. It should connect to MetaMask and alert you with the current MetaColony CLNY balance. 65 | 66 | 67 | 68 | 69 | ```javascript 70 | const { providers, Wallet } = require('ethers'); 71 | const { ColonyNetwork } = require('@colony/sdk'); 72 | 73 | const provider = new providers.JsonRpcProvider('https://xdai.colony.io/rpc/'); 74 | const wallet = Wallet.createRandom().connect(provider); 75 | 76 | // Get the Colony's XDAI funding in the ROOT pot (id 1) 77 | const start = async () => { 78 | // Create a new connection to the Colony Network contracts using the MetaMask "wallet" 79 | const colonyNetwork = await ColonyNetwork.init(wallet); 80 | // Connect to the MetaColony (this could be replaced with your own colony using `colonyNetwork.getColony(COLONY_ADDRESS)`) 81 | const metaColony = await colonyNetwork.getMetaColony(); 82 | // Get the CLNY funding for the MetaColony (CLNY is it's native token) 83 | const funding = await metaColony.getBalance(); 84 | // The funding will be in wei (x * 10^18), so we format into a readable string using the `toEth` function 85 | console.log('MetaColony balance is ' + toEth(funding) + ' CLNY') 86 | }; 87 | 88 | start(); 89 | ``` 90 | 91 | Run this file by executing `node index.js` and carefully observe the output. The last line should be something like 92 | 93 | ``` 94 | MetaColony balance is 2.125319999999999999 CLNY 95 | ``` 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/TxCreator/MetaTxCreator.ts: -------------------------------------------------------------------------------- 1 | import { IBasicMetaTransaction, Network } from '@colony/colony-js'; 2 | import { utils } from 'ethers'; 3 | import { MetadataType } from '@colony/colony-event-metadata-parser'; 4 | 5 | import { ParsedLogTransactionReceipt } from '../types'; 6 | 7 | import { BaseContract, EventData, TxCreator } from './TxCreator'; 8 | 9 | const { arrayify, solidityKeccak256, splitSignature } = utils; 10 | 11 | type MetaTxFunctions = IBasicMetaTransaction['functions']; 12 | type MetaTxInterface = IBasicMetaTransaction['interface']; 13 | 14 | interface Functions extends MetaTxFunctions { 15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 16 | [key: string]: (...args: any[]) => Promise; 17 | } 18 | interface Interface extends MetaTxInterface { 19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 20 | encodeFunctionData(functionFragment: string, values: any[]): string; 21 | } 22 | 23 | export interface MetaTxBaseContract extends BaseContract { 24 | functions: Functions; 25 | interface: Interface; 26 | } 27 | 28 | /** 29 | * An umbrella API for all kinds of transactions 30 | * 31 | * The `MetaTxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network. The `MetaTxCreator` supports sending a standard transaction ([[MetaTxCreator.tx]]) as well as metatransactions ([[MetaTxCreator.metaTx]]). 32 | * 33 | * ## Create a standard transaction ("force" in dApp) 34 | * 35 | * - [[MetaTxCreator.tx]]: force a Colony transaction, knowing you have the permissions to do so 36 | * - [[MetaTxCreator.metaTx]]: same as `tx()`, but send as a gasless metatransaction 37 | * 38 | * Learn more about these functions in their individual documentation 39 | */ 40 | export class MetaTxCreator< 41 | C extends MetaTxBaseContract, 42 | M extends keyof C['functions'], 43 | E extends EventData, 44 | MD extends MetadataType, 45 | > extends TxCreator { 46 | protected async sendMetaTransaction( 47 | encodedTransaction: string, 48 | target: string, 49 | ): Promise { 50 | if (!this.colonyNetwork.config.metaTxBroadcasterEndpoint) { 51 | throw new Error( 52 | `No metatransaction broadcaster endpoint found for network ${this.colonyNetwork.network}`, 53 | ); 54 | } 55 | 56 | const signer = this.colonyNetwork.getSigner(); 57 | const { provider } = signer; 58 | 59 | if (!provider) { 60 | throw new Error('No provider found'); 61 | } 62 | 63 | let chainId: number; 64 | 65 | if (this.colonyNetwork.network === Network.Custom) { 66 | chainId = 1; 67 | } else { 68 | const networkInfo = await provider.getNetwork(); 69 | chainId = networkInfo.chainId; 70 | } 71 | 72 | const userAddress = await signer.getAddress(); 73 | const nonce = await this.contract.functions.getMetatransactionNonce( 74 | userAddress, 75 | ); 76 | 77 | const message = solidityKeccak256( 78 | ['uint256', 'address', 'uint256', 'bytes'], 79 | [nonce.toString(), target, chainId, encodedTransaction], 80 | ); 81 | const buf = arrayify(message); 82 | const signature = await signer.signMessage(buf); 83 | const { r, s, v } = splitSignature(signature); 84 | 85 | const broadcastData = { 86 | target, 87 | payload: encodedTransaction, 88 | userAddress, 89 | r, 90 | s, 91 | v, 92 | }; 93 | 94 | return this.broadcastMetaTx(broadcastData); 95 | } 96 | 97 | /** 98 | * Forces an action using a gasless metatransaction 99 | * 100 | * @remarks The user sending this transaction has to have the appropriate permissions to do so. Learn more about permissions in Colony [here](/develop/dev-learning/permissions). 101 | * 102 | * @returns A tupel of event data and contract receipt (and a function to retrieve metadata if applicable) 103 | */ 104 | async metaTx() { 105 | const args = await this.getArgs(); 106 | 107 | const encodedTransaction = this.contract.interface.encodeFunctionData( 108 | this.method, 109 | args, 110 | ); 111 | const receipt = await this.sendMetaTransaction( 112 | encodedTransaction, 113 | this.contract.address, 114 | ); 115 | return this.getEventData(receipt); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/ColonyNetwork/ColonyToken.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | LogSetAuthorityEventObject, 3 | LogSetOwnerEventObject, 4 | } from '@colony/colony-js/events'; 5 | 6 | import { 7 | ColonyToken as ColonyTokenType, 8 | ColonyTokenFactory, 9 | } from '@colony/colony-js/tokens'; 10 | 11 | import { extractEvent } from '../utils'; 12 | import { ColonyNetwork } from './ColonyNetwork'; 13 | import { ERC20Token } from './ERC20Token'; 14 | 15 | export class ColonyToken extends ERC20Token { 16 | protected tokenClient: ColonyTokenType; 17 | 18 | /** 19 | * Creates a new instance of a Colony deployed Token 20 | * 21 | * @remarks This does not deploy a new token, only connects to an exisiting one 22 | * 23 | * @param colonyNetwork - A [[ColonyNetwork]] instance 24 | * @param token - A token address or a full contract (like on a colony token client) 25 | * @returns An ERC20 token abstraction instance 26 | */ 27 | constructor(colonyNetwork: ColonyNetwork, token: ColonyTokenType | string) { 28 | super(colonyNetwork, token); 29 | if (typeof token == 'string') { 30 | this.tokenClient = ColonyTokenFactory.connect( 31 | token, 32 | colonyNetwork.signerOrProvider, 33 | ); 34 | } else { 35 | this.tokenClient = token; 36 | } 37 | this.address = this.tokenClient.address; 38 | this.colonyNetwork = colonyNetwork; 39 | } 40 | 41 | /** 42 | * Provide direct access to the internally used ColonyJS TokenClient client. Only use when you know what you're doing 43 | * @internal 44 | * 45 | * @returns The internally used TokenClient 46 | */ 47 | getInternalTokenClient(): ColonyTokenType { 48 | return this.tokenClient; 49 | } 50 | 51 | /** 52 | * Sets the address of the TokenAuthority for this token 53 | * 54 | * Set the TokenAuthority for this token. Only has to be done once, after the TokenAuthority has been deployed. See [[Colony.deployTokenAuthority]] for more information. 55 | * 56 | * @remarks 57 | * Only works for native tokens deployed with Colony (not imported tokens). 58 | * 59 | * @example 60 | * ```typescript 61 | * import { w } from '@colony/sdk'; 62 | * 63 | * // Immediately executing async function 64 | * (async function() { 65 | * // Deploy the TokenAuthority contract 66 | * // (forced transaction example) 67 | * const [{ tokenAuthorityAddress }] = await colony.deployTokenAuthority().tx(); 68 | * // Set the TokenAuthority for this token 69 | * // (forced transaction example) 70 | * await colony.token.setAuthority(tokenAuthorityAddress).tx(); 71 | * })(); 72 | * ``` 73 | * 74 | * @param address - Address of the TokenAuthority contract 75 | * 76 | * @returns A transaction creator 77 | * 78 | * #### Event data 79 | * 80 | * | Property | Type | Description | 81 | * | :------ | :------ | :------ | 82 | * | `authority` | string | The address of the tokenAuthority that has been set | 83 | */ 84 | setAuthority(address: string) { 85 | return this.colonyNetwork.createMetaTxCreator( 86 | this.tokenClient, 87 | 'setAuthority', 88 | [address], 89 | async (receipt) => ({ 90 | ...extractEvent('LogSetAuthority', receipt), 91 | }), 92 | ); 93 | } 94 | 95 | /** 96 | * Sets the owner of the token 97 | * 98 | * Set the owner address for this token. Should usually be the colony. This will allow the Colony to always affect certain token parameters, event without the TokenAuthority deployed or used 99 | * 100 | * @remarks 101 | * Only works for native tokens deployed with Colony (not imported tokens). 102 | * 103 | * @param address - Address to set as the owner of the token (usually the colony) 104 | * 105 | * @returns A transaction creator 106 | * 107 | * #### Event data 108 | * 109 | * | Property | Type | Description | 110 | * | :------ | :------ | :------ | 111 | * | `owner` | string | The address of the owner that has been set | 112 | */ 113 | setOwner(address: string) { 114 | return this.colonyNetwork.createMetaTxCreator( 115 | this.tokenClient, 116 | 'setOwner', 117 | [address], 118 | async (receipt) => ({ 119 | ...extractEvent('LogSetOwner', receipt), 120 | }), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/TxCreator/EIP2612TxCreator.ts: -------------------------------------------------------------------------------- 1 | import { Network } from '@colony/colony-js'; 2 | import { ERC2612Token as ERC2612TokenType } from '@colony/colony-js/tokens'; 3 | import { BigNumberish, Signer, utils } from 'ethers'; 4 | import { MetadataType } from '@colony/colony-event-metadata-parser'; 5 | 6 | import type { TypedDataSigner } from '@ethersproject/abstract-signer'; 7 | 8 | import { ParsedLogTransactionReceipt } from '../types'; 9 | 10 | import { EventData, TxCreator } from './TxCreator'; 11 | 12 | const { splitSignature } = utils; 13 | 14 | // Little fix until ethers exposes this function 15 | interface TDSigner extends Signer, TypedDataSigner {} 16 | 17 | /** 18 | * An umbrella API for all kinds of transactions 19 | * 20 | * The `MetaTxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network. The `MetaTxCreator` supports sending a standard transaction ([[MetaTxCreator.force]]) as well as metatransactions ([[TxCreator.forceMeta]]). 21 | * 22 | * ## Create a standard transaction ("force" in dApp) 23 | * 24 | * - [[EIP2612TxCreator.tx]]: force a Colony transaction, knowing you have the permissions to do so 25 | * - [[EIP2612TxCreator.metaTx]]: same as `tx()`, but send as a gasless metatransaction 26 | * 27 | * Learn more about these functions in their individual documentation 28 | */ 29 | export class EIP2612TxCreator< 30 | E extends EventData, 31 | MD extends MetadataType, 32 | > extends TxCreator { 33 | protected async sendMetaTransaction( 34 | target: string, 35 | [spender, amount]: [string, BigNumberish], 36 | ): Promise { 37 | if (!this.colonyNetwork.config.metaTxBroadcasterEndpoint) { 38 | throw new Error( 39 | `No metatransaction broadcaster endpoint found for network ${this.colonyNetwork.network}`, 40 | ); 41 | } 42 | 43 | const signer = this.colonyNetwork.getSigner() as TDSigner; 44 | const { provider } = signer; 45 | 46 | if (!provider) { 47 | throw new Error('No provider found'); 48 | } 49 | 50 | let chainId: number; 51 | 52 | if (this.colonyNetwork.network === Network.Custom) { 53 | chainId = 1; 54 | } else { 55 | const networkInfo = await provider.getNetwork(); 56 | chainId = networkInfo.chainId; 57 | } 58 | 59 | const userAddress = await signer.getAddress(); 60 | const nonce = await this.contract.functions.nonces(userAddress); 61 | const tokenName = await this.contract.name(); 62 | /* 63 | * @NOTE One hour in the future from now 64 | * Time is in seconds 65 | */ 66 | const deadline = Math.floor(Date.now() / 1000) + 3600; 67 | 68 | // eslint-disable-next-line no-underscore-dangle 69 | const signature = await signer._signTypedData( 70 | { 71 | name: tokenName, // token.name() 72 | version: '1', 73 | chainId, 74 | verifyingContract: this.contract.address, 75 | }, 76 | { 77 | Permit: [ 78 | { name: 'owner', type: 'address' }, 79 | { name: 'spender', type: 'address' }, 80 | { name: 'value', type: 'uint256' }, 81 | { name: 'nonce', type: 'uint256' }, 82 | { name: 'deadline', type: 'uint256' }, 83 | ], 84 | }, 85 | { 86 | owner: userAddress, 87 | spender, 88 | value: amount, 89 | nonce: nonce.toString(), 90 | deadline, 91 | }, 92 | ); 93 | 94 | const { r, s, v } = splitSignature(signature); 95 | 96 | const broadcastData = { 97 | target, 98 | owner: userAddress, 99 | spender, 100 | value: amount.toString(), 101 | deadline, 102 | r, 103 | s, 104 | v, 105 | }; 106 | 107 | return this.broadcastMetaTx(broadcastData); 108 | } 109 | 110 | async tx() { 111 | if (this.method === 'permit') { 112 | throw new Error( 113 | `Only MetaTransactions are supported for this method. Please use "approve" instead.`, 114 | ); 115 | } 116 | return super.tx.apply(this); 117 | } 118 | 119 | /** 120 | * Forces an action using a gasless metatransaction 121 | * 122 | * @remarks The user sending this transaction has to have the appropriate permissions to do so. Learn more about permissions in Colony [here](/develop/dev-learning/permissions). 123 | * 124 | * @returns A tupel of event data and contract receipt (and a function to retrieve metadata if applicable) 125 | */ 126 | async metaTx() { 127 | if (this.method !== 'permit') { 128 | throw new Error( 129 | `Only the "permit" function is allowed to be sent as MetaTransaction per EIP-2612`, 130 | ); 131 | } 132 | 133 | const args = await this.getArgs(); 134 | 135 | const receipt = await this.sendMetaTransaction( 136 | this.contract.address, 137 | args as [string, BigNumberish], 138 | ); 139 | return this.getEventData(receipt); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish, ContractReceipt, utils } from 'ethers'; 2 | import { ColonyRole } from '@colony/colony-js'; 3 | 4 | import type { Log } from '@ethersproject/abstract-provider'; 5 | import type { JsonRpcProvider } from '@ethersproject/providers'; 6 | import type { Interface } from '@ethersproject/abi'; 7 | 8 | import { 9 | Ethers6Filter, 10 | Ethers6FilterByBlockHash, 11 | ParsedLogTransactionReceipt, 12 | } from './types'; 13 | 14 | /** Extract event args from a contract receipt */ 15 | // TODO: Make it possible to retrieve multiple events of the same kind 16 | export const extractEvent = ( 17 | eventName: string, 18 | receipt: ContractReceipt | ParsedLogTransactionReceipt, 19 | ): T | undefined => { 20 | if ('events' in receipt && receipt.events) { 21 | const event = receipt.events.find((ev) => ev.event === eventName); 22 | if (event?.args) { 23 | return event.args as ReadonlyArray & T; 24 | } 25 | } else if ('parsedLogs' in receipt && receipt.parsedLogs) { 26 | const event = receipt.parsedLogs.find((ev) => ev.name === eventName); 27 | if (event?.args) { 28 | return event.args as ReadonlyArray & T; 29 | } 30 | } 31 | return undefined; 32 | }; 33 | 34 | /** Manually extract an event using the interface (e.g. if emitting contract is a different one than the calling contract) */ 35 | export const extractCustomEvent = ( 36 | eventName: string, 37 | receipt: ContractReceipt | ParsedLogTransactionReceipt, 38 | iface: Interface, 39 | ): T | undefined => { 40 | let events: { data: string; topics: string[] }[]; 41 | if ('events' in receipt && receipt.events) { 42 | events = receipt.events; 43 | } else if ('logs' in receipt && receipt.logs) { 44 | events = receipt.logs; 45 | } else { 46 | events = []; 47 | } 48 | for (let i = 0; i < events.length; i += 1) { 49 | try { 50 | return iface.decodeEventLog( 51 | eventName, 52 | events[i].data, 53 | events[i].topics, 54 | ) as ReadonlyArray & T; 55 | } catch (e) { 56 | // ignore 57 | } 58 | } 59 | return undefined; 60 | }; 61 | 62 | /** Version of `getLogs` that also supports filtering for multiple addresses */ 63 | export const getLogs = async ( 64 | filter: 65 | | Ethers6Filter 66 | | Ethers6FilterByBlockHash 67 | | Promise, 68 | provider: JsonRpcProvider, 69 | ): Promise => { 70 | const usedFilter = await filter; 71 | return provider.send('eth_getLogs', [usedFilter]); 72 | }; 73 | 74 | /** Check if two addresses are equal */ 75 | export const addressesAreEqual = (a: string, b: string) => 76 | a.toLowerCase() === b.toLowerCase(); 77 | 78 | /** Use this to filter empty undefinied values from arrays in a type-safe way */ 79 | export const nonNullable = (value: T): value is NonNullable => { 80 | return value !== null && value !== undefined; 81 | }; 82 | 83 | /** 84 | * Convert any number to ETH (remove 18 zeros) 85 | * 86 | * @example 87 | * ```typescript 88 | * import { toEth } from '@colony/sdk'; 89 | * 90 | * const oneEther = BigNumber.from("1000000000000000000"); 91 | * console.log(toEth(oneEther)); // 1.0 92 | * ``` 93 | */ 94 | export const toEth = (num: BigNumberish) => utils.formatEther(num); 95 | 96 | /** 97 | * Convert any number to wei (add 18 zeros) 98 | * 99 | * @example 100 | * ```typescript 101 | * import { toWei } from '@colony/sdk'; 102 | * 103 | * const oneEther = '1.0'; 104 | * console.log(toWei(oneEther)); // { BigNumber: "1000000000000000000" } 105 | * ``` 106 | */ 107 | export const toWei = (num: string) => utils.parseEther(num); 108 | 109 | /** 110 | * Short-hand method to convert a number to wei using JS tagged template strings 111 | * 112 | * See also here: http://tc39wiki.calculist.org/es6/template-strings/ 113 | * 114 | * @remarks 115 | * This is only useful in contexts where the number is hard-coded (e.g. examples) 116 | * 117 | * @example 118 | * ```typescript 119 | * import { w } from '@colony/sdk'; 120 | * 121 | * console.log(w`1.0`); // { BigNumber: "1000000000000000000" } 122 | * ``` 123 | */ 124 | export const w = (str: TemplateStringsArray) => toWei(str[0]); 125 | 126 | /** 127 | * Parses a binary role integer into a [[ColonyRole]] array 128 | * 129 | * When getting multiple roles from contract methods or events they are 130 | * usually formatted as a binary number. Here the least significant bit is 131 | * the role with the index 0 (Recovery). 132 | * 133 | * E.g. 5 = 0b00101 equals Recovery and Arbitration 134 | * 135 | * This function parses these binary integers into a [[ColonyRole]] array. 136 | * 137 | * @param roles - A hex string (e.g. 0x3 = 0b11 equals Recovery and Root roles) 138 | * 139 | */ 140 | export const parseRoles = (roles: string) => { 141 | const rolesNum = parseInt(roles, 16); 142 | const result = [] as ColonyRole[]; 143 | for (let i = 0; i < ColonyRole.LAST_ROLE; i += 1) { 144 | // eslint-disable-next-line no-bitwise 145 | if (rolesNum & (1 << i)) { 146 | result.push(i as ColonyRole); 147 | } 148 | } 149 | return result; 150 | }; 151 | -------------------------------------------------------------------------------- /src/ipfs/IpfsMetadata.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch'; 2 | import wrapFetch from 'fetch-retry'; 3 | import { 4 | Metadata, 5 | MetadataType, 6 | getAnnotationMsgFromResponse, 7 | getColonyMetadataFromResponse, 8 | getDomainMetadataFromResponse, 9 | getStringForMetadataAnnotation, 10 | getStringForMetadataColony, 11 | getStringForMetadataDomain, 12 | getDecisionDetailsFromResponse, 13 | getMiscDataFromResponse, 14 | getStringForMetadataDecision, 15 | getStringForMetadataMisc, 16 | } from '@colony/colony-event-metadata-parser'; 17 | 18 | import IpfsAdapter from './IpfsAdapter'; 19 | import CloudflareReadonlyAdapter from './CloudflareReadonlyAdapter'; 20 | 21 | const fetchRetry = wrapFetch(fetch, { 22 | headers: { 23 | Accept: 'application/json', 24 | }, 25 | retryOn: [404, 503], 26 | retries: 3, 27 | retryDelay: 5000, 28 | }); 29 | 30 | export const IPFS_METADATA_EVENTS = { 31 | [MetadataType.Annotation]: 'Annotation(address,bytes32,string)', 32 | [MetadataType.Colony]: 'ColonyMetadata(address,string)', 33 | [MetadataType.Decision]: 'Annotation(address,bytes32,string)', 34 | [MetadataType.Domain]: 'DomainMetadata(address,uint256,string)', 35 | [MetadataType.Misc]: '', 36 | } as const; 37 | 38 | const IPFS_METADATA_PARSERS = { 39 | [MetadataType.Annotation]: getAnnotationMsgFromResponse, 40 | [MetadataType.Colony]: getColonyMetadataFromResponse, 41 | [MetadataType.Domain]: getDomainMetadataFromResponse, 42 | [MetadataType.Decision]: getDecisionDetailsFromResponse, 43 | [MetadataType.Misc]: getMiscDataFromResponse, 44 | None: () => { 45 | // Deliberately empty 46 | }, 47 | } as const; 48 | 49 | const IPFS_METADATA_ENCODERS = { 50 | [MetadataType.Annotation]: getStringForMetadataAnnotation, 51 | [MetadataType.Colony]: getStringForMetadataColony, 52 | [MetadataType.Decision]: getStringForMetadataDecision, 53 | [MetadataType.Domain]: getStringForMetadataDomain, 54 | [MetadataType.Misc]: getStringForMetadataMisc, 55 | } as const; 56 | 57 | export type MetadataEvent = 58 | typeof IPFS_METADATA_EVENTS[K]; 59 | export type MetadataValue = ReturnType< 60 | typeof IPFS_METADATA_PARSERS[K] 61 | >; 62 | 63 | /** 64 | * IpfsMetadata 65 | * 66 | * This is part of the [[ColonyNetwork]] and [[ColonyEventManager]] classes and not to be meant to instantiated directly. 67 | * You can find an instance of this under `colonyNetwork.ipfs` or `eventManager.ipfs` 68 | * 69 | * @internal 70 | * 71 | */ 72 | export class IpfsMetadata { 73 | private adapter: IpfsAdapter; 74 | 75 | constructor(adapter?: IpfsAdapter) { 76 | this.adapter = adapter || new CloudflareReadonlyAdapter(); 77 | } 78 | 79 | static eventSupportsMetadata(eventName: string) { 80 | if (Object.keys(IPFS_METADATA_EVENTS).includes(eventName)) { 81 | return true; 82 | } 83 | return false; 84 | } 85 | 86 | async getMetadata( 87 | type: K, 88 | cid: string, 89 | ): Promise> { 90 | const url = this.adapter.getIpfsUrl(cid); 91 | const res = await fetchRetry(url); 92 | try { 93 | const data = await res.text(); 94 | return IPFS_METADATA_PARSERS[type](data) as MetadataValue; 95 | } catch (e) { 96 | throw new Error( 97 | `Could not parse IPFS metadata. Original error: ${ 98 | (e as Error).message 99 | }`, 100 | ); 101 | } 102 | } 103 | 104 | async getMetadataForEvent>( 105 | eventName: E, 106 | cid: string, 107 | ): Promise> { 108 | const url = this.adapter.getIpfsUrl(cid); 109 | const res = await fetchRetry(url); 110 | try { 111 | const data = await res.text(); 112 | const entry = Object.entries(IPFS_METADATA_EVENTS).find( 113 | ([, value]) => value === eventName, 114 | ); 115 | if (!entry) { 116 | throw new Error(`Not a valid MetadataEvent: ${eventName}`); 117 | } 118 | const metadataType = entry[0] as T; 119 | 120 | return IPFS_METADATA_PARSERS[metadataType](data) as MetadataValue; 121 | } catch (e) { 122 | throw new Error( 123 | `Could not parse IPFS metadata. Original error: ${ 124 | (e as Error).message 125 | }`, 126 | ); 127 | } 128 | } 129 | 130 | async getRawMetadata(cid: string): Promise { 131 | const url = this.adapter.getIpfsUrl(cid); 132 | const res = await fetchRetry(url); 133 | try { 134 | const json = await res.json(); 135 | return json as Metadata; 136 | } catch (e) { 137 | throw new Error( 138 | `Could not parse IPFS metadata. Original error: ${ 139 | (e as Error).message 140 | }`, 141 | ); 142 | } 143 | } 144 | 145 | async uploadMetadata( 146 | type: T, 147 | data: Parameters[0], 148 | ) { 149 | const str = IPFS_METADATA_ENCODERS[type]( 150 | // @ts-expect-error Is it possible to type this properly without repeating all the checks from the parser library? 151 | data, 152 | ); 153 | return this.adapter.uploadJson(str); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /docs/getting-started/your-first-transaction.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: In this guide we look at a more involved example. We create a team within a colony and inspect the results of this transaction. 3 | 4 | sidebar_position: 0 5 | --- 6 | 7 | # Creating your first transaction 8 | 9 | In this example we're going through the process of creating a team (domain) within a colony using only Colony SDK. It is meant to run inside a browser with the MetaMask extension installed. If you need a template (with build system) to start from, take a look at [colonyStarter](https://github.com/JoinColony/colonyStarter). 10 | 11 | ## Connecting to an existing colony 12 | 13 | For this example we assume that you want to connect to a Colony that already exists and the MetaMask account we are using has the appropriate permissions (*Architecture* role in the Root team). 14 | 15 | ```typescript 16 | import { providers } from 'ethers'; 17 | import { ColonyNetwork } from '@colony/sdk'; 18 | 19 | // If MetaMask is installed there will be an `ethereum` object on the `window` 20 | // NOTE: Make sure MetaMask is connected to Gnosis chain (see https://docs.gnosischain.com/tools/wallets/metamask) 21 | const provider = new providers.Web3Provider(window.ethereum); 22 | 23 | // We are calling a bunch of async functions so we are creating this wrapper function 24 | const start = async () => { 25 | // This will try to connect the page to MetaMask 26 | await provider.send('eth_requestAccounts', []); 27 | // Create a new connection to the Colony Network contracts using the MetaMask "wallet" 28 | const colonyNetwork = await ColonyNetwork.init(provider.getSigner()); 29 | // Connect to a colony (replace the address with the address of the colony you'd like to use) 30 | // This is the devdemo colony (https://xdai.colony.io/colony/devdemo) 31 | const colony = await colonyNetwork.getColony('0x364B3153A24bb9ECa28B8c7aCeB15E3942eb4fc5'); 32 | }; 33 | 34 | start(); 35 | ``` 36 | 37 | ## Creating the team's metadata object 38 | 39 | Let's prepare some metadata for the colony details. We created a [`DomainMetadata`](../api/interfaces/DomainMetadata.md) object and stored it under the IPFS hash [`QmTwksWE2Zn4icTvk5E7QZb1vucGNuu5GUCFZ361r8gKXM`](https://gateway.pinata.cloud/ipfs/QmTwksWE2Zn4icTvk5E7QZb1vucGNuu5GUCFZ361r8gKXM): 40 | 41 | ```json 42 | { 43 | "version": 2, 44 | "name": "domain", 45 | "data": { 46 | "domainName": "A-Team", 47 | "domainColor": 0, 48 | "domainPurpose": "Pass Butter" 49 | } 50 | } 51 | ``` 52 | 53 | :::info 54 | Storing data on chain is expensive. Metadata is stored on IPFS and our solution to enrich on-chain data with additional information and only used in very few methods around Colony. **We also provide ways to automatically create and store metadata for your transaction.** See [Metadata](../guides/metadata.md) for more information. 55 | ::: 56 | 57 | ## Creating the team 58 | 59 | Let's imagine we continue in our async function `start` from above: 60 | 61 | ```typescript 62 | const [{ domainId }, receipt, getMetadata] = await colony.createTeam('QmTwksWE2Zn4icTvk5E7QZb1vucGNuu5GUCFZ361r8gKXM').tx(); 63 | ``` 64 | 65 | You can see that calling the `tx()` function on the [`TxCreator`](../api/classes/TxCreator.md) returns a tuple with three entries: 66 | 67 | ### Event data 68 | 69 | The first entry is the Ethereum event data, emitted by the contract transaction, already parsed by ethers, so that you can directly access it on the object. To find out what event data is returned by a transaction you can either rely on the TypeScript types or look up the relevant [Event data](../api/classes/Colony.md#event-data-3) section in the documentation of the corresponding method. This entry exists on all methods that emit events on contracts (almost all of them). 70 | 71 | ### Contract receipt 72 | 73 | The second entry is a receipt object. This is the `ethers` `ContractReceipt` (see [the ethers docs](https://docs.ethers.org/v5/api/providers/types/#providers-TransactionReceipt)) that was issues during the transaction. 74 | 75 | ### Metadata getter function 76 | 77 | The third entry is a function that can be called to get that metadata that was created or attached to the transaction events. This is usually not very interesting in this context as we just stored the metadata. Note that this is only available in very few functions that have metadata attached to them. Here's how you'd use this function: 78 | 79 | ```typescript 80 | const { domainName, domainColor, domainPurpose } = await getMetadata(); 81 | ``` 82 | 83 | Read more about metadata within Colony [here](../guides/metadata.md). 84 | 85 | ## Where to go from here? 86 | 87 | Congrats on creating your first transaction with Colony SDK! This is just the beginning! 88 | 89 | Make sure you're making yourself familiar with [how transactions work in Colony SDK](../guides/transactions.md) and then feel free to check out the [Colony API docs](../api/classes/Colony.md). There's lots to explore there! 90 | 91 | :::tip Examples! Examples! Examples! 92 | Yes, we have plenty of examples for you to explore on how to do all sorts of things in Colony SDK. Want to create a motion or decision? Want to see how reputation works? Check out our [browser examples](https://github.com/JoinColony/colonySDK/tree/main/examples/browser/src) or if you prefer to run things on the server side, we also have [NodeJS based examples](https://github.com/JoinColony/colonySDK/tree/main/examples/node). By the way, we have deployed the browser based examples [here](https://joincolony.github.io/colonySDK/) for you to try them immediately. 93 | ::: 94 | -------------------------------------------------------------------------------- /docs/guides/colony-creation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: A guide on how to create a colony programmatically. The deployment of a colony requires a handful of transactions for it to be up and running and fully usable. This guide explains how to go through the whole process using Colony SDK 3 | 4 | sidebar_position: 2 5 | --- 6 | 7 | # Creating a colony 8 | 9 | Even though deploying a Colony is technically just a matter of issuing one transaction, for the colony to be properly set up and usable in the dApp, some extra steps are necessary. In this guide we'll walk you through the whole process of creating the right transactions and explain what happens on the way. 10 | 11 | **Keep in mind that some of these transactions are optional and depend on your specific situation.** 12 | 13 | 14 | For a full example see [here](https://github.com/JoinColony/colonySDK/blob/main/examples/node/create.ts). 15 | 16 | :::info 17 | 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()`. 18 | ::: 19 | 20 | ## Step 1 - Deploying the Colony contract (and optionally its token) 21 | 22 | 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): 23 | 24 | ```typescript 25 | // Create actual colony (deploys Colony contract) 26 | const [{ colonyAddress, tokenAddress, tokenAuthorityAddress }] = await colonyNetwork 27 | .createColony({ name: 'Test token', symbol: 'TOT' }, 'colonytestname') 28 | .tx(); 29 | ``` 30 | 31 | 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). 32 | 33 | 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: 34 | 35 | ```typescript 36 | // Use USDC on Gnosis chain as the native token 37 | const [{ colonyAddress }] = await colonyNetwork 38 | .createColony('0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', 'anothertestname') 39 | .tx(); 40 | ``` 41 | 42 | 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. 43 | 44 | **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. 45 | 46 | ## Step 2 - Instantiate the Colony for the first time 47 | 48 | Let's instantiate the colony (this is the code used to instantiate an existing colony) and the token: 49 | 50 | ```typescript 51 | const colony = await colonyNetwork.getColony(colonyAddress); 52 | const { token } = colony; 53 | ``` 54 | 55 | ## Step 3 - Set the Colony as owner of the token 56 | 57 | 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. 58 | 59 | ```typescript 60 | // Set the token's authority to the freshly deployed one (see step 1) 61 | await token.setAuthority(tokenAuthorityAddress).tx(); 62 | // Set the token's owner (the colony), to have permissions to execute authorized functions (like `mint`) 63 | await colony.token.setOwner(colony.address).tx(); 64 | ``` 65 | 66 | ## Step 4 - Install the `OneTxPayment` extension 67 | 68 | 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: 69 | 70 | ```typescript 71 | const [{ extensionId, version }] = await colony 72 | .installExtension(SupportedExtension.oneTx) 73 | .tx(); 74 | await colony.updateExtensions(); 75 | const [{ user, setTo, role }] = await colony 76 | .setRoles(colony.ext.oneTx.address, [ 77 | ColonyRole.Administration, 78 | ColonyRole.Funding, 79 | ]) 80 | .tx(); 81 | ``` 82 | 83 | Here we install the extension using the `installExtension` method. This extension is an own contract that was deployed in this transaction. To get its address, we re-initialize the extensions on the colony using `updateExtensions`. After that, `oneTx` will be available on `colony.ext`. 84 | Finally, we assign the **Administration** and **Funding** roles of the colony's `Root` team to the extension that we just deployed. The OneTxPayment extension needs these permissions to function properly. 85 | 86 | ## That's it! 87 | 88 | We have successfully deployed a colony that can be used from the dApp as well. Explore what's possible within a colony using Colony SDK [here](../api/classes/Colony.md). 89 | -------------------------------------------------------------------------------- /docs/api/classes/OneTxPayment.md: -------------------------------------------------------------------------------- 1 | # Class: OneTxPayment 2 | 3 | ## `OneTxPayment` (One Transaction Payment) 4 | 5 | Ordinarily payments require more than one transaction, because the payment lifecycle requires more than one permissioned role. 6 | 7 | In some use cases, there might be a need for one authorized individual to be able to create, funds, and finalize a payment within a single transaction. 8 | 9 | The OneTxPayment extension adds this functionality by adding a makePayment function which requires the caller to have both Funding and administration ability within the domain of the payment. 10 | 11 | Extension therefore requires Administration and Funding roles to function. 12 | 13 | Note: if you deployed your Colony using the Dapp, the OneTxPayment extension is already installed for you 14 | 15 | ## Constructors 16 | 17 | ### constructor 18 | 19 | • **new OneTxPayment**(`colony`, `oneTxPaymentClient`) 20 | 21 | #### Parameters 22 | 23 | | Name | Type | 24 | | :------ | :------ | 25 | | `colony` | [`Colony`](Colony.md) | 26 | | `oneTxPaymentClient` | `OneTxPaymentClientV4` | 27 | 28 | ## Properties 29 | 30 | ### address 31 | 32 | • **address**: `string` 33 | 34 | ___ 35 | 36 | ### version 37 | 38 | • **version**: `number` 39 | 40 | ___ 41 | 42 | ### extensionType 43 | 44 | ▪ `Static` **extensionType**: [`OneTxPayment`](../enums/Extension.md#onetxpayment) = `Extension.OneTxPayment` 45 | 46 | ___ 47 | 48 | ### supportedVersions 49 | 50 | ▪ `Static` **supportedVersions**: ``4``[] 51 | 52 | ## Methods 53 | 54 | ### pay 55 | 56 | ▸ **pay**(`recipient`, `amount`, `teamId?`, `tokenAddress?`): [`ColonyTxCreator`](ColonyTxCreator.md)<`OneTxPaymentClientV4`, ``"makePaymentFundedFromDomain"``, { `agent?`: `string` ; `fundamentalId?`: `BigNumber` ; `nPayouts?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 57 | 58 | Make a payment to a single or multiple addresses using one or more tokens 59 | 60 | **`Remarks`** 61 | 62 | Requires the `OneTxPayment` extension to be installed for the Colony (this is usually the case for Colonies created via the Dapp). Note that most tokens use 18 decimals, so add a bunch of zeros or use our `w` or `toWei` functions (see example) 63 | 64 | **`Example`** 65 | 66 | ```typescript 67 | import { Id, Tokens, w } from '@colony/sdk'; 68 | 69 | // Immediately executing async function 70 | (async function() { 71 | // Pay 10 XDAI (on Gnosis chain) from the root domain to the following address 72 | // (forced transaction example) 73 | await colony.ext.oneTx.pay( 74 | '0xb77D57F4959eAfA0339424b83FcFaf9c15407461', 75 | w`10`, 76 | Id.RootDomain, 77 | Tokens.Gnosis.XDAI, 78 | ).tx(); 79 | })(); 80 | ``` 81 | 82 | #### Parameters 83 | 84 | | Name | Type | Description | 85 | | :------ | :------ | :------ | 86 | | `recipient` | `string` \| `string`[] | Wallet address of account to send the funds to (also awarded reputation when sending the native token) - can also be an array of addresses to pay | 87 | | `amount` | `BigNumberish` \| `BigNumberish`[] | Amount to pay in wei - can also be an array of amounts for the different tokens | 88 | | `teamId?` | `BigNumberish` | The team to use to send the funds from. Has to have funding of at least the amount you need to send. See [Colony.moveFundsToTeam](Colony.md#movefundstoteam). Defaults to the Colony's root team | 89 | | `tokenAddress?` | `string` \| `string`[] | The address of the token to make the payment in. Default is the Colony's native token - can also be an array of token addresses (needs to be the same length as `amount`) | 90 | 91 | #### Returns 92 | 93 | [`ColonyTxCreator`](ColonyTxCreator.md)<`OneTxPaymentClientV4`, ``"makePaymentFundedFromDomain"``, { `agent?`: `string` ; `fundamentalId?`: `BigNumber` ; `nPayouts?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 94 | 95 | A transaction creator 96 | 97 | #### Event data 98 | 99 | | Property | Type | Description | 100 | | :------ | :------ | :------ | 101 | | `agent` | string | The address that is responsible for triggering this event | 102 | | `fundamentalId` | BigNumber | The newly added payment id | 103 | | `nPayouts` | BigNumber | Number of payouts in total | 104 | 105 | ___ 106 | 107 | ### upgrade 108 | 109 | ▸ **upgrade**(`toVersion?`): [`ColonyTxCreator`](ColonyTxCreator.md)<`ColonyClientV12`, ``"upgradeExtension"``, { `colony?`: `string` ; `extensionId?`: `string` ; `version?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 110 | 111 | Upgrade this extension to the next or a custom version 112 | 113 | This method upgrades this extension to a specified version or, if no version is provided to the next higher version. 114 | 115 | **`Remarks`** 116 | 117 | * Only users with *Root* role are allowed to upgrade an extension (or another extension with appropriate permissions) 118 | * Downgrading of extensions is not possible 119 | 120 | #### Parameters 121 | 122 | | Name | Type | Description | 123 | | :------ | :------ | :------ | 124 | | `toVersion?` | `BigNumberish` | Specify a custom version to upgrade the extension to | 125 | 126 | #### Returns 127 | 128 | [`ColonyTxCreator`](ColonyTxCreator.md)<`ColonyClientV12`, ``"upgradeExtension"``, { `colony?`: `string` ; `extensionId?`: `string` ; `version?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 129 | 130 | A transaction creator 131 | 132 | #### Event data 133 | 134 | | Property | Type | Description | 135 | | :------ | :------ | :------ | 136 | | `extensionId` | string | Extension id (name of the extension) that was upgraded | 137 | | `oldVersion` | BigNumber | Version of the colony before the upgrade | 138 | | `newVersion` | BigNumber | Version of the colony after the upgrade | 139 | 140 | ___ 141 | 142 | ### getLatestSupportedVersion 143 | 144 | ▸ `Static` **getLatestSupportedVersion**(): ``4`` 145 | 146 | #### Returns 147 | 148 | ``4`` 149 | -------------------------------------------------------------------------------- /docs/api/classes/MetaTxCreator.md: -------------------------------------------------------------------------------- 1 | # Class: MetaTxCreator 2 | 3 | An umbrella API for all kinds of transactions 4 | 5 | The `MetaTxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network. The `MetaTxCreator` supports sending a standard transaction ([MetaTxCreator.tx](MetaTxCreator.md#tx)) as well as metatransactions ([MetaTxCreator.metaTx](MetaTxCreator.md#metatx)). 6 | 7 | ## Create a standard transaction ("force" in dApp) 8 | 9 | - [MetaTxCreator.tx](MetaTxCreator.md#tx): force a Colony transaction, knowing you have the permissions to do so 10 | - [MetaTxCreator.metaTx](MetaTxCreator.md#metatx): same as `tx()`, but send as a gasless metatransaction 11 | 12 | Learn more about these functions in their individual documentation 13 | 14 | ## Type parameters 15 | 16 | | Name | Type | 17 | | :------ | :------ | 18 | | `C` | extends [`MetaTxBaseContract`](../interfaces/MetaTxBaseContract.md) | 19 | | `M` | extends keyof `C`[``"functions"``] | 20 | | `E` | extends `EventData` | 21 | | `MD` | extends [`MetadataType`](../enums/MetadataType.md) | 22 | 23 | ## Hierarchy 24 | 25 | - [`TxCreator`](TxCreator.md)<`C`, `M`, `E`, `MD`\> 26 | 27 | ↳ **`MetaTxCreator`** 28 | 29 | ↳↳ [`ColonyTxCreator`](ColonyTxCreator.md) 30 | 31 | ## Constructors 32 | 33 | ### constructor 34 | 35 | • **new MetaTxCreator**<`C`, `M`, `E`, `MD`\>(`__namedParameters`) 36 | 37 | #### Type parameters 38 | 39 | | Name | Type | 40 | | :------ | :------ | 41 | | `C` | extends [`MetaTxBaseContract`](../interfaces/MetaTxBaseContract.md) | 42 | | `M` | extends `string` \| `number` \| `symbol` | 43 | | `E` | extends `EventData` | 44 | | `MD` | extends [`MetadataType`](../enums/MetadataType.md) | 45 | 46 | #### Parameters 47 | 48 | | Name | Type | 49 | | :------ | :------ | 50 | | `__namedParameters` | `Object` | 51 | | `__namedParameters.args` | `unknown`[] \| () => `Promise`<`unknown`[]\> | 52 | | `__namedParameters.colonyNetwork` | [`ColonyNetwork`](ColonyNetwork.md) | 53 | | `__namedParameters.contract` | `C` | 54 | | `__namedParameters.eventData?` | (`receipt`: `ContractReceipt`) => `Promise`<`E`\> | 55 | | `__namedParameters.metadataType?` | `MD` | 56 | | `__namedParameters.method` | `M` | 57 | | `__namedParameters.txConfig?` | [`TxConfig`](../interfaces/TxConfig.md)<`MD`\> | 58 | 59 | #### Inherited from 60 | 61 | [TxCreator](TxCreator.md).[constructor](TxCreator.md#constructor) 62 | 63 | ## Methods 64 | 65 | ### metaTx 66 | 67 | ▸ **metaTx**(): `Promise`<[`E`, [`ParsedLogTransactionReceipt`](../interfaces/ParsedLogTransactionReceipt.md), () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](../interfaces/ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](../interfaces/DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](../interfaces/DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`MD`]\>\>] \| [`E`, [`ParsedLogTransactionReceipt`](../interfaces/ParsedLogTransactionReceipt.md)]\> 68 | 69 | Forces an action using a gasless metatransaction 70 | 71 | **`Remarks`** 72 | 73 | The user sending this transaction has to have the appropriate permissions to do so. Learn more about permissions in Colony [here](/develop/dev-learning/permissions). 74 | 75 | #### Returns 76 | 77 | `Promise`<[`E`, [`ParsedLogTransactionReceipt`](../interfaces/ParsedLogTransactionReceipt.md), () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](../interfaces/ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](../interfaces/DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](../interfaces/DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`MD`]\>\>] \| [`E`, [`ParsedLogTransactionReceipt`](../interfaces/ParsedLogTransactionReceipt.md)]\> 78 | 79 | A tupel of event data and contract receipt (and a function to retrieve metadata if applicable) 80 | 81 | ___ 82 | 83 | ### tx 84 | 85 | ▸ **tx**(): `Promise`<[`E`, `ContractReceipt`, () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](../interfaces/ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](../interfaces/DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](../interfaces/DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`MD`]\>\>] \| [`E`, `ContractReceipt`]\> 86 | 87 | Create a standard transaction ("force" in dApp) 88 | 89 | **`Remarks`** 90 | 91 | The user sending this transaction has to have the appropriate permissions to do so. Learn more about permissions in Colony [here](/develop/dev-learning/permissions). 92 | 93 | #### Returns 94 | 95 | `Promise`<[`E`, `ContractReceipt`, () => `Promise`<`ReturnType`<{ `None`: () => `void` ; `annotation`: (`res`: `string`) => `string` = getAnnotationMsgFromResponse; `colony`: (`res`: `string`) => [`ColonyMetadata`](../interfaces/ColonyMetadata.md) = getColonyMetadataFromResponse; `decision`: (`res`: `string`) => [`DecisionMetadata`](../interfaces/DecisionMetadata.md) = getDecisionDetailsFromResponse; `domain`: (`res`: `string`) => [`DomainMetadata`](../interfaces/DomainMetadata.md) = getDomainMetadataFromResponse; `misc`: (`res`: `string`) => `MiscMetadata` = getMiscDataFromResponse }[`MD`]\>\>] \| [`E`, `ContractReceipt`]\> 96 | 97 | A tupel of event data and contract receipt (and a function to retrieve metadata if applicable) 98 | 99 | #### Inherited from 100 | 101 | [TxCreator](TxCreator.md).[tx](TxCreator.md#tx) 102 | -------------------------------------------------------------------------------- /src/ColonyNetwork/TokenLocking.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | UserTokenDeposited_address_address_uint256_EventObject, 3 | UserTokenWithdrawnEventObject, 4 | } from '@colony/colony-js/events'; 5 | 6 | import { BigNumberish } from 'ethers'; 7 | import { TokenLockingClient } from '@colony/colony-js/'; 8 | 9 | import { extractEvent } from '../utils'; 10 | import { ColonyNetwork } from './ColonyNetwork'; 11 | 12 | export class TokenLocking { 13 | address: string; 14 | 15 | private colonyNetwork: ColonyNetwork; 16 | 17 | private tokenLockingClient: TokenLockingClient; 18 | 19 | constructor( 20 | colonyNetwork: ColonyNetwork, 21 | tokenLockingClient: TokenLockingClient, 22 | ) { 23 | this.address = tokenLockingClient.address; 24 | this.colonyNetwork = colonyNetwork; 25 | this.tokenLockingClient = tokenLockingClient; 26 | } 27 | 28 | /** 29 | * Provide direct access to the internally used TokenLocking client. Only use when you know what you're doing 30 | * 31 | * @internal 32 | * @returns The internally used TokenLockingClient 33 | */ 34 | getInternalTokenLockingClient(): TokenLockingClient { 35 | return this.tokenLockingClient; 36 | } 37 | 38 | /** 39 | * Deposit `amount` of the wallet owners holdings of the Colony's native token into the Colony. 40 | * 41 | * In order for the wallet owner to stake tokens, that amount has to be approved and deposited into the Colony first. In the dapp the process is called "Activation" of a certain amount of the Colony's native token. The wallet must hold at least the amount of the token that will be deposited. 42 | * 43 | * @example 44 | * ```typescript 45 | * import { w } from '@colony/sdk'; 46 | * 47 | * // Immediately executing async function 48 | * (async function() { 49 | * const token = await colony.getToken(); 50 | * // Approve 100 tokens for the Colony Network to activate 51 | * await token.approve(w`100`).force(); 52 | * // Deposit the tokens 53 | * await colonyNetwork.locking.deposit(token.address, w`100`).force(); 54 | * })(); 55 | * ``` 56 | * 57 | * @param tokenAddress - Token to be deposited 58 | * @param amount - Amount of the token to be deposited 59 | * 60 | * @returns A transaction creator 61 | * 62 | * #### Event data 63 | * 64 | * | Property | Type | Description | 65 | * | :------ | :------ | :------ | 66 | * | `token` | string | The address of the Colony's native token | 67 | * | `user` | string | The address that deposited the tokens from their wallet | 68 | * | `amount` | BigNumber | Amount that was deposited | 69 | */ 70 | deposit(tokenAddress: string, amount: BigNumberish) { 71 | return this.colonyNetwork.createMetaTxCreator( 72 | this.tokenLockingClient, 73 | 'deposit(address,uint256,bool)', 74 | [tokenAddress, amount, false], 75 | async (receipt) => ({ 76 | ...extractEvent( 77 | 'UserTokenDeposited', 78 | receipt, 79 | ), 80 | }), 81 | ); 82 | } 83 | 84 | /** 85 | * Withdraw `amount` of the wallet owners holdings of the Colony's native token from the Colony. 86 | * 87 | * Does the opposite of `deposit` and frees the deposited tokens back to the wallet address. 88 | * 89 | * @example 90 | * ```typescript 91 | * import { w } from '@colony/sdk'; 92 | * 93 | * // Immediately executing async function 94 | * (async function() { 95 | * const token = await colony.getToken(); 96 | * // Withdraw 100 tokens that were previously deposited 97 | * await colonyNetwork.locking.withdraw(token.address, w`100`).force(); 98 | * })(); 99 | * ``` 100 | * 101 | * @param tokenAddress - Token to be withdrawn 102 | * @param amount - Amount of the token to be withdrawn 103 | * 104 | * @returns A transaction creator 105 | * 106 | * #### Event data 107 | * 108 | * | Property | Type | Description | 109 | * | :------ | :------ | :------ | 110 | * | `token` | string | The address of the Colony's native token | 111 | * | `user` | string | The address that withdrew the tokens from their wallet | 112 | * | `amount` | BigNumber | Amount that was withdrawn | 113 | */ 114 | withdraw(tokenAddress: string, amount: BigNumberish) { 115 | return this.colonyNetwork.createMetaTxCreator( 116 | this.tokenLockingClient, 117 | 'withdraw(address,uint256,bool)', 118 | [tokenAddress, amount, false], 119 | async (receipt) => ({ 120 | ...extractEvent( 121 | 'UserTokenWithdrawn', 122 | receipt, 123 | ), 124 | }), 125 | ); 126 | } 127 | 128 | /** 129 | * Get the wallet owner's deposited and locked balance of the Colony's native token. 130 | * 131 | * This method will show the accumulated amount that was deposited using the [[ColonyToken.deposit]] method 132 | * 133 | * @param tokenAddress - Token to check 134 | * @param user - The wallet address that we want to check the deposited amount of 135 | * 136 | * @returns The currently deposited balance of the Colony's native token 137 | */ 138 | async getUserDeposit(tokenAddress: string, user: string) { 139 | const userLock = await this.tokenLockingClient.getUserLock( 140 | tokenAddress, 141 | user, 142 | ); 143 | return userLock.balance; 144 | } 145 | 146 | /** 147 | * Get the wallet owner's approved balance of the Colony's native token for an obliator (i.e. an extension) 148 | * 149 | * This method will show the accumulated amount that was approved using the [[ColonyToken.approve]] method 150 | * 151 | * @param tokenAddress - Token to check 152 | * @param user - The wallet address that we want to check the approved amount of 153 | * @param obligator - The address that has been approved to obligate the funds. 154 | * 155 | * @returns The currently approved balance of the Colony's native token for the obligator 156 | */ 157 | async getUserApproval(tokenAddress: string, user: string, obligator: string) { 158 | return this.tokenLockingClient.getApproval(user, tokenAddress, obligator); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/TxCreator/TxCreator.ts: -------------------------------------------------------------------------------- 1 | import { Contract, ContractReceipt, ContractTransaction } from 'ethers'; 2 | import { MetadataType } from '@colony/colony-event-metadata-parser'; 3 | import { fetch } from 'cross-fetch'; 4 | 5 | import { MetadataValue } from '../ipfs'; 6 | import { ParsedLogTransactionReceipt } from '../types'; 7 | import { IPFS_METADATA_EVENTS } from '../ipfs/IpfsMetadata'; 8 | import { ColonyNetwork } from '../ColonyNetwork'; 9 | import { nonNullable } from '../utils'; 10 | 11 | export interface TxConfig { 12 | metadataType?: M; 13 | } 14 | 15 | export interface EventData { 16 | metadata?: string; 17 | } 18 | 19 | export interface TxCreatorConfig { 20 | colonyNetwork: ColonyNetwork; 21 | contract: C; 22 | method: M; 23 | args: unknown[] | (() => Promise); 24 | eventData?: (receipt: ContractReceipt) => Promise; 25 | metadataType?: MD; 26 | txConfig?: TxConfig; 27 | } 28 | 29 | // I really just wanted to use the ethers' `BaseContract` but that seemed to have been to specific 30 | export interface BaseContract { 31 | address: string; 32 | functions: { 33 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 34 | [key: string]: (...args: any[]) => Promise; 35 | }; 36 | interface: Contract['interface']; 37 | } 38 | 39 | /** 40 | * An umbrella API for all kinds of transactions 41 | * 42 | * The `TxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network. This is the base class of the TxCreator that only supports the `force()` action and no metatransactions. 43 | * 44 | * ## Create a standard transaction ("force" in dApp) 45 | * 46 | * - [[TxCreator.tx]]: force a Colony transaction, knowing you have the permissions to do so 47 | * 48 | * Learn more about these functions in their individual documentation 49 | */ 50 | export class TxCreator< 51 | C extends BaseContract, 52 | M extends keyof C['functions'], 53 | E extends EventData, 54 | MD extends MetadataType, 55 | > { 56 | protected colonyNetwork: ColonyNetwork; 57 | 58 | protected contract: C; 59 | 60 | protected method: string; 61 | 62 | protected args: unknown[] | (() => Promise); 63 | 64 | private eventData?: (receipt: ContractReceipt) => Promise; 65 | 66 | protected txConfig?: TxConfig; 67 | 68 | constructor({ 69 | colonyNetwork, 70 | contract, 71 | method, 72 | args, 73 | eventData, 74 | txConfig, 75 | }: { 76 | colonyNetwork: ColonyNetwork; 77 | contract: C; 78 | method: M; 79 | args: unknown[] | (() => Promise); 80 | eventData?: (receipt: ContractReceipt) => Promise; 81 | metadataType?: MD; 82 | txConfig?: TxConfig; 83 | }) { 84 | this.colonyNetwork = colonyNetwork; 85 | this.contract = contract; 86 | this.method = method as string; 87 | this.args = args; 88 | this.eventData = eventData; 89 | this.txConfig = txConfig; 90 | } 91 | 92 | protected async getArgs() { 93 | let args: unknown[] = []; 94 | 95 | if (typeof this.args == 'function') { 96 | args = await this.args(); 97 | } else { 98 | args = this.args; 99 | } 100 | 101 | return args; 102 | } 103 | 104 | protected async getEventData< 105 | R extends ContractReceipt | ParsedLogTransactionReceipt, 106 | >(receipt: R): Promise<[E, R, () => Promise>] | [E, R]> { 107 | if (this.eventData) { 108 | const data = await this.eventData(receipt); 109 | 110 | if (this.txConfig?.metadataType && data.metadata) { 111 | const getMetadata = this.colonyNetwork.ipfs.getMetadataForEvent.bind( 112 | this.colonyNetwork.ipfs, 113 | IPFS_METADATA_EVENTS[this.txConfig.metadataType], 114 | data.metadata, 115 | ) as () => Promise>; 116 | 117 | return [data, receipt as R, getMetadata]; 118 | } 119 | 120 | return [data, receipt as R]; 121 | } 122 | 123 | return [{} as E, receipt as R]; 124 | } 125 | 126 | protected async broadcastMetaTx(broadcastData: Record) { 127 | const signer = this.colonyNetwork.getSigner(); 128 | const { provider } = signer; 129 | 130 | if (!provider) { 131 | throw new Error('No provider found'); 132 | } 133 | 134 | const res = await fetch( 135 | `${this.colonyNetwork.config.metaTxBroadcasterEndpoint}/broadcast`, 136 | { 137 | method: 'POST', 138 | headers: { 139 | 'Content-Type': 'application/json', 140 | }, 141 | body: JSON.stringify(broadcastData), 142 | }, 143 | ); 144 | const parsed = await res.json(); 145 | 146 | if (parsed.status !== 'success') { 147 | throw new Error( 148 | `Could not send Metatransaction. Reason given: ${parsed.data.reason}`, 149 | ); 150 | } 151 | 152 | if (!parsed.data?.txHash) { 153 | throw new Error( 154 | 'Could not get transaction hash from broadcaster response', 155 | ); 156 | } 157 | 158 | const receipt = (await provider.waitForTransaction( 159 | parsed.data.txHash, 160 | )) as ParsedLogTransactionReceipt; 161 | receipt.parsedLogs = receipt.logs 162 | .map((log) => { 163 | try { 164 | return this.contract.interface.parseLog(log); 165 | } catch (e) { 166 | return null; 167 | } 168 | }) 169 | .filter(nonNullable); 170 | 171 | return receipt; 172 | } 173 | 174 | /** 175 | * Create a standard transaction ("force" in dApp) 176 | * 177 | * @remarks The user sending this transaction has to have the appropriate permissions to do so. Learn more about permissions in Colony [here](/develop/dev-learning/permissions). 178 | * 179 | * @returns A tupel of event data and contract receipt (and a function to retrieve metadata if applicable) 180 | */ 181 | async tx() { 182 | const args = await this.getArgs(); 183 | 184 | const tx = (await this.contract.functions[this.method].apply( 185 | this.contract, 186 | args, 187 | )) as ContractTransaction; 188 | const receipt = await tx.wait(); 189 | return this.getEventData(receipt); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /examples/browser/src/local-reputation.ts: -------------------------------------------------------------------------------- 1 | import { providers, utils, Wallet } from 'ethers'; 2 | 3 | import { Colony, ColonyNetwork, toEth, toWei, w } from '../../../src'; 4 | import { setupOneTxPaymentExtension } from '../../helpers'; 5 | 6 | const { isAddress } = utils; 7 | const provider = new providers.JsonRpcProvider('http://127.0.0.1:8545'); 8 | 9 | let metaColony: Colony; 10 | let recipient: string; 11 | 12 | const getWallet = () => { 13 | // This is the private key of the ganache account with index 0: 0xb77D57F4959eAfA0339424b83FcFaf9c15407461. In the contract deployments done with truffle this account is used as the owner of the MetaColony, so we have all the permissions. This will effectively replace MetaMask 14 | return new Wallet( 15 | '0x0355596cdb5e5242ad082c4fe3f8bbe48c9dba843fe1f99dd8272f487e70efae', 16 | provider, 17 | ); 18 | }; 19 | 20 | // Instantiate a colony client, connected to the local MetaColony and Reputation Oracle 21 | const getMetaColony = async (networkAddress: string) => { 22 | const signer = getWallet(); 23 | const colonyNetwork = await ColonyNetwork.init(signer, { 24 | networkClientOptions: { 25 | networkAddress, 26 | reputationOracleEndpoint: 'http://localhost:3000', 27 | }, 28 | }); 29 | // Get an instance of the MetaColony 30 | const colony = await colonyNetwork.getMetaColony(); 31 | // Set up the OneTxPayment extension. This is usually already done for Colonys that were created with the Dapp 32 | await setupOneTxPaymentExtension(colony); 33 | return colony; 34 | }; 35 | 36 | // Mint CLNY and fund the Colony with it 37 | const fundColony = async (amount: string) => { 38 | // Mint `amount` CLNY 39 | await metaColony.mint(toWei(amount)).tx(); 40 | // Claim the CLNY for the MetaColony (important!) 41 | await metaColony.claimFunds().tx(); 42 | // Look up the funds 43 | const funding = await metaColony.getBalance(); 44 | return toEth(funding); 45 | }; 46 | 47 | // Make a payment to the given user in the MetaColony's native token (CLNY). This will cause the user to have reputation in the new domain after the next reputation mining cycle (max 24h) 48 | const makePayment = async (to: string) => { 49 | if (!metaColony.ext.oneTx) { 50 | throw new Error('OneTxPayment extension not installed'); 51 | } 52 | // Pay 10 CLNY to the recipient 53 | return metaColony.ext.oneTx.pay(to, w`10`).tx(); 54 | }; 55 | 56 | // We're using Ganache's evm_increaseTime and evm_mine methods to first increase the block time artificially by one hour and then force a block to mine. This will trigger the local reputation oracle/miner to award the pending reputation. 57 | const jumpIntoTheFuture = async () => { 58 | await provider.send('evm_increaseTime', [3600]); 59 | await provider.send('evm_mine', []); 60 | await provider.send('evm_mine', []); 61 | }; 62 | 63 | const getReputation = async (userAddress: string): Promise => { 64 | const reputation = await metaColony.getReputation(userAddress); 65 | return reputation.toString(); 66 | }; 67 | 68 | // Some setup to display the UI 69 | const inputAddress: HTMLInputElement | null = 70 | document.querySelector('#address'); 71 | const buttonConnect = document.querySelector('#button_connect'); 72 | const inputFunding: HTMLInputElement | null = 73 | document.querySelector('#funding_amount'); 74 | const buttonFund = document.querySelector('#button_fund'); 75 | const inputRecipient: HTMLInputElement | null = 76 | document.querySelector('#recipient'); 77 | const buttonPay = document.querySelector('#button_pay'); 78 | const buttonJump = document.querySelector('#button_jump'); 79 | const buttonReputation = document.querySelector('#button_get_reputation'); 80 | 81 | const errElm: HTMLParagraphElement | null = document.querySelector('#error'); 82 | const resultElm: HTMLParagraphElement | null = 83 | document.querySelector('#result'); 84 | 85 | if ( 86 | !inputAddress || 87 | !inputFunding || 88 | !inputRecipient || 89 | !errElm || 90 | !resultElm || 91 | !buttonConnect || 92 | !buttonFund || 93 | !buttonPay || 94 | !buttonJump || 95 | !buttonReputation 96 | ) { 97 | throw new Error('Could not find all required HTML elements'); 98 | } 99 | 100 | const panik = (err: Error) => { 101 | errElm.innerText = `Found an error: ${err.message}`; 102 | console.error(err); 103 | }; 104 | const kalm = () => { 105 | errElm.innerText = ''; 106 | }; 107 | const speak = (msg: string) => { 108 | resultElm.innerText = msg; 109 | }; 110 | 111 | buttonConnect.addEventListener('click', async () => { 112 | kalm(); 113 | const etherRouterAddress = inputAddress.value; 114 | if (!isAddress(etherRouterAddress)) { 115 | return panik(new Error('This is not a valid address')); 116 | } 117 | speak('Processing...'); 118 | try { 119 | const colony = await getMetaColony(inputAddress.value); 120 | metaColony = colony; 121 | speak(` 122 | Connected to Colony with address: ${colony.address}. 123 | Colony version: ${colony.version}. 124 | `); 125 | } catch (e) { 126 | panik(e as Error); 127 | speak(''); 128 | } finally { 129 | inputAddress.value = ''; 130 | } 131 | return null; 132 | }); 133 | 134 | buttonFund.addEventListener('click', async () => { 135 | kalm(); 136 | speak('Processing...'); 137 | try { 138 | const fundingAmount = inputFunding.value; 139 | const funding = await fundColony(fundingAmount); 140 | speak(`Funded MetaColony! Current funding: ${funding} CLNY`); 141 | } catch (e) { 142 | panik(e as Error); 143 | speak(''); 144 | } finally { 145 | inputFunding.value = ''; 146 | } 147 | return null; 148 | }); 149 | 150 | buttonPay.addEventListener('click', async () => { 151 | recipient = inputRecipient.value; 152 | speak('Processing...'); 153 | try { 154 | await makePayment(recipient); 155 | } catch (e) { 156 | panik(e as Error); 157 | speak(''); 158 | } 159 | speak(`Successfully paid 10 CLNY to ${recipient}`); 160 | }); 161 | 162 | buttonJump.addEventListener('click', async () => { 163 | await jumpIntoTheFuture(); 164 | speak('Whooo that was a hell of a ride. Welcome to the future'); 165 | }); 166 | 167 | buttonReputation.addEventListener('click', async () => { 168 | const reputation = await getReputation(recipient); 169 | speak(`User ${recipient} has ${toEth(reputation)} reputation points`); 170 | }); 171 | -------------------------------------------------------------------------------- /docs/api/classes/ERC20Token.md: -------------------------------------------------------------------------------- 1 | # Class: ERC20Token 2 | 3 | ## Hierarchy 4 | 5 | - **`ERC20Token`** 6 | 7 | ↳ [`ColonyToken`](ColonyToken.md) 8 | 9 | ↳ [`ERC2612Token`](ERC2612Token.md) 10 | 11 | ## Constructors 12 | 13 | ### constructor 14 | 15 | • **new ERC20Token**(`colonyNetwork`, `token`) 16 | 17 | Creates a new instance of an ERC20 Token 18 | 19 | **`Remarks`** 20 | 21 | This does not deploy a new token, only connects to an exisiting one 22 | 23 | #### Parameters 24 | 25 | | Name | Type | Description | 26 | | :------ | :------ | :------ | 27 | | `colonyNetwork` | [`ColonyNetwork`](ColonyNetwork.md) | A [ColonyNetwork](ColonyNetwork.md) instance | 28 | | `token` | `string` \| `TokenERC20` | A token address or a full contract (like on a colony token client) | 29 | 30 | ## Properties 31 | 32 | ### address 33 | 34 | • **address**: `string` 35 | 36 | ## Methods 37 | 38 | ### allowance 39 | 40 | ▸ **allowance**(`owner`, `spender`): `Promise`<`BigNumber`\> 41 | 42 | Returns the amount which `spender` is still allowed to withdraw from `owner` 43 | 44 | #### Parameters 45 | 46 | | Name | Type | 47 | | :------ | :------ | 48 | | `owner` | `string` | 49 | | `spender` | `string` | 50 | 51 | #### Returns 52 | 53 | `Promise`<`BigNumber`\> 54 | 55 | The allowance amount 56 | 57 | ___ 58 | 59 | ### approve 60 | 61 | ▸ **approve**(`amount`, `spender?`): [`TxCreator`](TxCreator.md)<`TokenERC20`, ``"approve"``, { `guy?`: `string` ; `src?`: `string` ; `wad?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 62 | 63 | Approve `amount` of the wallet owners holdings of the specified token 64 | 65 | In order for the wallet owner to stake tokens, that amount has to be approved and deposited into the Colony first. In the dapp the process is called "Activation" of a certain amount of the Colony's native token. The wallet must hold at least the amount of the token that will be approved. 66 | 67 | **`Example`** 68 | 69 | ```typescript 70 | import { w } from '@colony/sdk'; 71 | 72 | // Immediately executing async function 73 | (async function() { 74 | // Approve 100 tokens to be "activated" 75 | await colony.token.approve(w`100`).force(); 76 | // Deposit the tokens 77 | await colonyNetwork.locking.deposit(token.address, w`100`).force(); 78 | })(); 79 | ``` 80 | 81 | #### Parameters 82 | 83 | | Name | Type | Description | 84 | | :------ | :------ | :------ | 85 | | `amount` | `BigNumberish` | Amount of the token to be approved | 86 | | `spender?` | `string` | Spender to approve the amount for. Defaults to the Colony Network | 87 | 88 | #### Returns 89 | 90 | [`TxCreator`](TxCreator.md)<`TokenERC20`, ``"approve"``, { `guy?`: `string` ; `src?`: `string` ; `wad?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 91 | 92 | A transaction creator 93 | 94 | #### Event data 95 | 96 | | Property | Type | Description | 97 | | :------ | :------ | :------ | 98 | | `src` | string | The address that approved the tokens from their wallet | 99 | | `guy` | string | Address of the TokenLocking contract | 100 | | `wad` | BigNumber | Amount that was approved | 101 | 102 | ___ 103 | 104 | ### balanceOf 105 | 106 | ▸ **balanceOf**(`owner`): `Promise`<`BigNumber`\> 107 | 108 | Returns the account balance of another account with address `owner` 109 | 110 | #### Parameters 111 | 112 | | Name | Type | 113 | | :------ | :------ | 114 | | `owner` | `string` | 115 | 116 | #### Returns 117 | 118 | `Promise`<`BigNumber`\> 119 | 120 | The account balance of the corresponding address 121 | 122 | ___ 123 | 124 | ### decimals 125 | 126 | ▸ **decimals**(): `Promise`<`number`\> 127 | 128 | Returns the token's decimals 129 | 130 | #### Returns 131 | 132 | `Promise`<`number`\> 133 | 134 | The token's decimals (e.g. 18) 135 | 136 | ___ 137 | 138 | ### name 139 | 140 | ▸ **name**(): `Promise`<`string`\> 141 | 142 | Returns the token's name 143 | 144 | #### Returns 145 | 146 | `Promise`<`string`\> 147 | 148 | The token's name (e.g. Colony Network Token) 149 | 150 | ___ 151 | 152 | ### symbol 153 | 154 | ▸ **symbol**(): `Promise`<`string`\> 155 | 156 | Returns the token's symbol 157 | 158 | #### Returns 159 | 160 | `Promise`<`string`\> 161 | 162 | The token's symbol (e.g. CLNY) 163 | 164 | ___ 165 | 166 | ### totalSupply 167 | 168 | ▸ **totalSupply**(): `Promise`<`BigNumber`\> 169 | 170 | Returns the total token supply 171 | 172 | #### Returns 173 | 174 | `Promise`<`BigNumber`\> 175 | 176 | The token's total supply 177 | 178 | ___ 179 | 180 | ### transfer 181 | 182 | ▸ **transfer**(`to`, `value`): [`TxCreator`](TxCreator.md)<`TokenERC20`, ``"transfer"``, { `dst?`: `string` ; `src?`: `string` ; `wad?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 183 | 184 | Transfers `value` amount of tokens to address `to` from the currently used wallet 185 | 186 | #### Parameters 187 | 188 | | Name | Type | 189 | | :------ | :------ | 190 | | `to` | `string` | 191 | | `value` | `BigNumberish` | 192 | 193 | #### Returns 194 | 195 | [`TxCreator`](TxCreator.md)<`TokenERC20`, ``"transfer"``, { `dst?`: `string` ; `src?`: `string` ; `wad?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 196 | 197 | A transaction creator 198 | 199 | #### Event data 200 | 201 | | Property | Type | Description | 202 | | :------ | :------ | :------ | 203 | | `src` | string | The address that transferred the tokens from their wallet | 204 | | `dst` | string | Address of the recipient of the tokens | 205 | | `wad` | BigNumber | Amount that was transferred | 206 | 207 | ___ 208 | 209 | ### transferFrom 210 | 211 | ▸ **transferFrom**(`from`, `to`, `value`): [`TxCreator`](TxCreator.md)<`TokenERC20`, ``"transferFrom"``, { `dst?`: `string` ; `src?`: `string` ; `wad?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 212 | 213 | Transfers `value` amount of tokens from address `from` to address `to` 214 | 215 | The transferFrom method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf. This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies 216 | 217 | #### Parameters 218 | 219 | | Name | Type | 220 | | :------ | :------ | 221 | | `from` | `string` | 222 | | `to` | `string` | 223 | | `value` | `BigNumberish` | 224 | 225 | #### Returns 226 | 227 | [`TxCreator`](TxCreator.md)<`TokenERC20`, ``"transferFrom"``, { `dst?`: `string` ; `src?`: `string` ; `wad?`: `BigNumber` }, [`MetadataType`](../enums/MetadataType.md)\> 228 | 229 | A transaction creator 230 | 231 | #### Event data 232 | 233 | | Property | Type | Description | 234 | | :------ | :------ | :------ | 235 | | `src` | string | The address that transferred the tokens from their wallet | 236 | | `dst` | string | Address of the recipient of the tokens | 237 | | `wad` | BigNumber | Amount that was transferred | 238 | -------------------------------------------------------------------------------- /src/ColonyNetwork/ERC20Token.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApprovalEventObject, 3 | TransferEventObject, 4 | } from '@colony/colony-js/events'; 5 | 6 | import { 7 | ERC20Token as ERC20TokenType, 8 | ERC20TokenFactory, 9 | } from '@colony/colony-js/tokens'; 10 | import type { BigNumberish } from 'ethers'; 11 | 12 | import { extractEvent } from '../utils'; 13 | import { ColonyNetwork } from './ColonyNetwork'; 14 | 15 | export class ERC20Token { 16 | protected colonyNetwork: ColonyNetwork; 17 | 18 | protected tokenClient: ERC20TokenType; 19 | 20 | address: string; 21 | 22 | /** 23 | * Creates a new instance of an ERC20 Token 24 | * 25 | * @remarks This does not deploy a new token, only connects to an exisiting one 26 | * 27 | * @param colonyNetwork - A [[ColonyNetwork]] instance 28 | * @param token - A token address or a full contract (like on a colony token client) 29 | * @returns An ERC20 token abstraction instance 30 | */ 31 | constructor(colonyNetwork: ColonyNetwork, token: ERC20TokenType | string) { 32 | if (typeof token == 'string') { 33 | this.tokenClient = ERC20TokenFactory.connect( 34 | token, 35 | colonyNetwork.signerOrProvider, 36 | ); 37 | } else { 38 | this.tokenClient = token; 39 | } 40 | this.address = this.tokenClient.address; 41 | this.colonyNetwork = colonyNetwork; 42 | } 43 | 44 | /** 45 | * Provide direct access to the internally used ColonyJS TokenClient client. Only use when you know what you're doing 46 | * @internal 47 | * 48 | * @returns The internally used TokenClient 49 | */ 50 | getInternalTokenClient(): ERC20TokenType { 51 | return this.tokenClient; 52 | } 53 | 54 | /** 55 | * Returns the token's name 56 | * 57 | * @returns The token's name (e.g. Colony Network Token) 58 | */ 59 | async name() { 60 | return this.tokenClient.name(); 61 | } 62 | 63 | /** 64 | * Returns the token's symbol 65 | * 66 | * @returns The token's symbol (e.g. CLNY) 67 | */ 68 | async symbol() { 69 | return this.tokenClient.symbol(); 70 | } 71 | 72 | /** 73 | * Returns the token's decimals 74 | * 75 | * @returns The token's decimals (e.g. 18) 76 | */ 77 | async decimals() { 78 | return this.tokenClient.decimals(); 79 | } 80 | 81 | /** 82 | * Returns the total token supply 83 | * 84 | * @returns The token's total supply 85 | */ 86 | async totalSupply() { 87 | return this.tokenClient.totalSupply(); 88 | } 89 | 90 | /** 91 | * Returns the account balance of another account with address `owner` 92 | * 93 | * @returns The account balance of the corresponding address 94 | */ 95 | async balanceOf(owner: string) { 96 | return this.tokenClient.balanceOf(owner); 97 | } 98 | 99 | /** 100 | * Returns the amount which `spender` is still allowed to withdraw from `owner` 101 | * 102 | * @returns The allowance amount 103 | */ 104 | async allowance(owner: string, spender: string) { 105 | return this.tokenClient.allowance(owner, spender); 106 | } 107 | 108 | /** 109 | * Transfers `value` amount of tokens to address `to` from the currently used wallet 110 | * 111 | * @returns A transaction creator 112 | * 113 | * #### Event data 114 | * 115 | * | Property | Type | Description | 116 | * | :------ | :------ | :------ | 117 | * | `src` | string | The address that transferred the tokens from their wallet | 118 | * | `dst` | string | Address of the recipient of the tokens | 119 | * | `wad` | BigNumber | Amount that was transferred | 120 | */ 121 | transfer(to: string, value: BigNumberish) { 122 | return this.colonyNetwork.createTxCreator( 123 | this.tokenClient, 124 | 'transfer', 125 | [to, value], 126 | async (receipt) => ({ 127 | ...extractEvent('Transfer', receipt), 128 | }), 129 | ); 130 | } 131 | 132 | /** 133 | * Transfers `value` amount of tokens from address `from` to address `to` 134 | * 135 | * The transferFrom method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf. This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies 136 | * 137 | * @returns A transaction creator 138 | * 139 | * #### Event data 140 | * 141 | * | Property | Type | Description | 142 | * | :------ | :------ | :------ | 143 | * | `src` | string | The address that transferred the tokens from their wallet | 144 | * | `dst` | string | Address of the recipient of the tokens | 145 | * | `wad` | BigNumber | Amount that was transferred | 146 | */ 147 | transferFrom(from: string, to: string, value: BigNumberish) { 148 | return this.colonyNetwork.createTxCreator( 149 | this.tokenClient, 150 | 'transferFrom', 151 | [from, to, value], 152 | async (receipt) => ({ 153 | ...extractEvent('Transfer', receipt), 154 | }), 155 | ); 156 | } 157 | 158 | /** 159 | * Approve `amount` of the wallet owners holdings of the specified token 160 | * 161 | * In order for the wallet owner to stake tokens, that amount has to be approved and deposited into the Colony first. In the dapp the process is called "Activation" of a certain amount of the Colony's native token. The wallet must hold at least the amount of the token that will be approved. 162 | * 163 | * @example 164 | * ```typescript 165 | * import { w } from '@colony/sdk'; 166 | * 167 | * // Immediately executing async function 168 | * (async function() { 169 | * // Approve 100 tokens to be "activated" 170 | * await colony.token.approve(w`100`).force(); 171 | * // Deposit the tokens 172 | * await colonyNetwork.locking.deposit(token.address, w`100`).force(); 173 | * })(); 174 | * ``` 175 | * 176 | * @param amount - Amount of the token to be approved 177 | * @param spender - Spender to approve the amount for. Defaults to the Colony Network 178 | * 179 | * @returns A transaction creator 180 | * 181 | * #### Event data 182 | * 183 | * | Property | Type | Description | 184 | * | :------ | :------ | :------ | 185 | * | `src` | string | The address that approved the tokens from their wallet | 186 | * | `guy` | string | Address of the TokenLocking contract | 187 | * | `wad` | BigNumber | Amount that was approved | 188 | */ 189 | approve(amount: BigNumberish, spender?: string) { 190 | const approvedSpender = spender || this.colonyNetwork.locking.address; 191 | return this.colonyNetwork.createTxCreator( 192 | this.tokenClient, 193 | 'approve', 194 | [approvedSpender, amount], 195 | async (receipt) => ({ 196 | ...extractEvent('Approval', receipt), 197 | }), 198 | ); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /examples/browser/web/local-motions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Colony SDK browser demo - local deployment - Motion workflow

11 |

First make sure that the Colony contracts are deployed locally and the Reputation Oracle is set 14 | up.

15 |

We'll be using wallet address 0xb77D57F4959eAfA0339424b83FcFaf9c15407461 to connect to the locally 16 | deployed MetaColony. This address was used to deploy the MetaColony locally and is the "owner" of it. Any 17 | transaction will automatically be signed as we have the private key hardcoded. No connection to MetaMask is 18 | needed. 19 |

20 |

1) Let's connect to the ColonyNetwork first Paste in the address from the 21 | `etherrouter-address.json` in the `colonyNetwork` directory

22 |

This will also set up the Motions&Disputes extension in the MetaColony with testing settings as well as 23 | distribute 24 | an initial amount of reputation and CLNY to the two addresses we're using for this example: 25 | 0xb77D57F4959eAfA0339424b83FcFaf9c15407461 and 0x9df24e73f40b2a911eb254a8825103723e13209c. 26 |

27 | 28 | 29 |

2) Now we install the VotingReputation extension and bootstrap some reputation (you only need to 30 | do 31 | this once for this example)

32 | 33 |

3) Now we approve some of the CLNY of 0xb77D57F4959eAfA0339424b83FcFaf9c15407461 for the Colony and 34 | the 35 | VotingReputation staking, which will remove it from the user's wallet (you only need to do this 36 | once for this example) 37 |

38 | 39 |

4) Jump forward in time - When we connected to the Colony above, the two addresses mentioned were awarded 40 | some reputation. It is awarded in cycles; in our case it's set to 1 hour. As we have full control over our local 41 | blockchain, Ganache allows us to travel to the future! Fasten your seatbelts, here we go (take 43 | a look at the console for the reputation oracle!)!

44 |

Instructions: 45 |

    46 |
  • i) Press the button
  • 47 |
  • ii) Wait for the message
    ✅ New reputation hash confirmed
    to appear in your console (reputation 48 | oracle)
  • 49 |
  • iii) Press the button again
  • 50 |
  • iv) Wait for the above message again
  • 51 |
  • v) Then go to the next step
  • 52 |
53 | 54 |

Note: We're using Ganache's evm_increaseTime and evm_mine methods to first increase the block time artificially by one hour and then force a 59 | block to mine. This will trigger the local reputation oracle/miner to award the pending reputation. We have to do 60 | that twice, if the reputation oracle was just started for the first time.

61 |

5) Then, we create a motion to pay some CLNY to a third address: 62 | 0x27ff0c145e191c22c75cd123c679c3e1f58a4469 (this will also incur reputation if the motion passes) (make it less than 100!) 64 |

65 | 66 | 67 |

6) Next, we look at the motion more closely

68 | 69 | 70 |

You will see the domainId in which the motion was created, the altTarget (which is the OneTxPayment extension in 71 | this case, as we want to execute the `pay` function on it), the encoded action and the stakes remaining for 72 | activation.

73 |

7) Okay, now we're ready to stake. Feel free to stake any amount, then get the motion again and see how it 74 | goes up :)

75 | 76 | 77 | 78 |

8) We can also simulate voting. For that, we activate both sides by staking the required amount on both 79 | sides from a single address. That usually won't happen in the wild (except if someone wants to force voting for 80 | some 81 | reason). Stake on both sides and then get the motion data to see if remainingYayStakes and 82 | remainingNayStakes are both 0.0 83 |

84 | 85 | 86 |

9) Reveal the vote After the voting phase is over (you will need to wait ~6 87 | minutes 88 | - we are forwarding time again for that), the votes can be revealed. Only if a vote is revealed it is counted and 89 | the voting reward is paid out

90 | 91 |

10) If the motion was successful, we can finalize it. In our example we can only finalize it if we wait 92 | long 93 | enough (~6min - the time is forwarded again when you press the button) after staking was only activated for Yay or 94 | if we voted for Yay.

95 | 96 |
97 |
98 | Message 99 |
100 |

101 |

102 |
103 |
104 | 105 | 106 | 107 | 108 | --------------------------------------------------------------------------------