Skip to content

Commit

Permalink
feat: add CustomContract class using abitype
Browse files Browse the repository at this point in the history
  • Loading branch information
chmanie committed Oct 23, 2024
1 parent 392e373 commit 226033e
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 9 deletions.
31 changes: 31 additions & 0 deletions packages/sdk/examples/node/custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { providers } from 'ethers';
import 'cross-fetch/dist/node-polyfill.js';
import { erc20Abi } from 'abitype/abis';

import { type Address } from 'abitype';
import {
ColonyNetwork,
ColonyRpcEndpoint,
CustomContract,
toEth,
} from '../../src/index.js';

const provider = new providers.JsonRpcProvider(ColonyRpcEndpoint.ArbitrumOne);

// Create a custom instance of the USDC token on Arbitrum One
const start = async () => {
const colonyNetwork = new ColonyNetwork(provider);
const usdc = new CustomContract(
colonyNetwork,
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
erc20Abi,
);
const metaColony = await colonyNetwork.getMetaColony();
const { address } = metaColony;
const [funds] = await usdc.read('balanceOf', [address as Address]);
console.info(
`${toEth(funds)} CLNY in root team of MetaColony with address: ${address}`,
);
};

start();
3 changes: 2 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"dependencies": {
"@colony/core": "^2.3.0",
"@colony/events": "^3.0.0",
"@colony/tokens": "^0.3.0"
"@colony/tokens": "^0.3.0",
"abitype": "^1.0.6"
},
"devDependencies": {
"@colony/contractor": "^2.2.0",
Expand Down
78 changes: 78 additions & 0 deletions packages/sdk/src/ColonyNetwork/CustomColonyNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
ColonyNetworkAddress,
MetaTxBroadCasterEndpoint,
ReputationOracleEndpoint,
} from '@colony/core';
import { type ContractInterface, Contract, Signer } from 'ethers';
import { IpfsMetadata, Network, type SignerOrProvider } from '../index.js';
import {
type ColonyNetworkConfig,
type ColonyNetworkOptions,
} from './ColonyNetwork.js';
import { type IColonyNetwork } from '../contracts/IColonyNetwork.js';

export class CustomColonyNetwork {
private networkContract: IColonyNetwork;

/** Configuration of the ColonyNetwork for later use */
/** @internal */
config: ColonyNetworkConfig;

/** The IPFS adapter for Metadata. Defaults to a read-only adapter */
ipfs: IpfsMetadata;

/** The network the client is connected to. Defaults to Gnosis chain */
network: Network;

/**
* An ethers.js [Signer](https://docs.ethers.org/v5/api/signer/#Signer) or [Provider](https://docs.ethers.org/v5/api/providers/).
*
* E.g. a [Wallet](https://docs.ethers.org/v5/api/signer/#Wallet) or a [Web3Provider](https://docs.ethers.org/v5/api/providers/other/#Web3Provider) (MetaMask)
*/
signerOrProvider: SignerOrProvider;

/**
* Creates a new custom instance of the custom ColonyNetwork
*
* @param signerOrProvider - An _ethers_ compatible Signer or Provider instance
* @param abi - JSON ABI of the custom ColonyNetwork contract
* @param options - Optional custom {@link ColonyNetworkOptions}
* @returns A ColonyNetwork abstraction instance
*/
constructor(
signerOrProvider: SignerOrProvider,
abi: ContractInterface,
options?: ColonyNetworkOptions,
) {
this.network = options?.network || Network.Gnosis;
this.ipfs = new IpfsMetadata(options?.ipfsAdapter);
this.config = {
metaTxBroadcasterEndpoint:
options?.metaTxBroadcasterEndpoint ||
MetaTxBroadCasterEndpoint[this.network],
reputationOracleEndpoint:
options?.reputationOracleEndpoint ||
ReputationOracleEndpoint[this.network],
};
// Here we need to create the contract with ethers
this.networkContract = new Contract(
options?.customNetworkAddress || ColonyNetworkAddress[this.network],
abi,
signerOrProvider,
) as IColonyNetwork;
this.signerOrProvider = signerOrProvider;
}

getSigner(): Signer {
if (!(this.signerOrProvider instanceof Signer)) {
throw new Error('Need a signer to create a transaction');
}
return this.signerOrProvider;
}

getInternalNetworkContract() {
return this.networkContract;
}
}

export default CustomColonyNetwork;
120 changes: 120 additions & 0 deletions packages/sdk/src/ColonyNetwork/CustomContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
type Abi,
type AbiParametersToPrimitiveTypes,
type Address,
type ExtractAbiFunction,
type ExtractAbiFunctionNames,
} from 'abitype';
import { Contract, type ContractInterface } from 'ethers';
import {
type ColonyNetwork,
type ContractReceipt,
type EventData,
type MetaTxBaseContract,
type TxConfig,
} from '../index.js';
import type CustomColonyNetwork from './CustomColonyNetwork.js';
import CustomTxCreator from '../TxCreator/CustomTxCreator.js';

export class CustomContract<A extends Abi> {
private abi: A;

private colonyNetwork: ColonyNetwork | CustomColonyNetwork;

private contract: MetaTxBaseContract;

address: Address;

/**
* Creates a new instance of a custom contract
*
* This is your main entry point to talk to the Colony Network Smart Contracts.
* From here you should be able to instantiate all the required instances for Colonies and their extensions.
*
* @param colonyNetwork - {@link ColonyNetwork} instance
* @param address - Address of the deployed contract
* @param abi - JSON ABI of the contract
* @returns A CustomContract instance
*/
constructor(
colonyNetwork: ColonyNetwork | CustomColonyNetwork,
address: Address,
abi: A,
) {
this.address = address;
this.abi = abi;

// We do a little bit of casting to make ethers happy with the abitype types
this.contract = new Contract(
address,
this.abi as unknown as ContractInterface,
colonyNetwork.signerOrProvider,
) as unknown as MetaTxBaseContract;
this.colonyNetwork = colonyNetwork;
}

/**
* Creates a new {@link CustomTxCreator} for custom, permissioned transactions or metatransactions
*
* @param method - The transaction method to execute on the contract
* @param args - The arguments for the method
* @param eventData - A function that extracts the relevant event data from the {@link ContractReceipt}
* @param txConfig - More configuration options, like {@link MetadataType} if the event contains metadata or if methods are unsupported
* @returns A {@link CustomTxCreator}
*/
createTxCreator<
M extends ExtractAbiFunctionNames<A, 'payable' | 'nonpayable'>,
E extends EventData,
>(
method: M,
args: AbiParametersToPrimitiveTypes<
ExtractAbiFunction<A, M>['inputs'],
'inputs'
>,
eventData?: (receipt: ContractReceipt) => Promise<E>,
txConfig?: TxConfig,
) {
return new CustomTxCreator<A, M, E>({
colonyNetwork: this.colonyNetwork,
contract: this.contract,
method,
args,
eventData,
txConfig,
});
}

/**
* Read a contract value
*
* @param method - The pure/view method to read from the contract
* @param args - The arguments for the method
* @returns A promise resolving into the specific type for the contract return value
*/
async read<M extends ExtractAbiFunctionNames<A, 'pure' | 'view'>>(
method: M,
args: AbiParametersToPrimitiveTypes<
ExtractAbiFunction<A, M>['inputs'],
'inputs'
>,
): Promise<
AbiParametersToPrimitiveTypes<
ExtractAbiFunction<A, M>['outputs'],
'outputs'
>
> {
// A little typecasting to make ethers happy
const customArgs = args as unknown[];
const contract = this.contract as unknown as Record<
string,
(...args: unknown[]) => Promise<unknown>
>;
const result = contract[method](...customArgs) as Promise<
AbiParametersToPrimitiveTypes<
ExtractAbiFunction<A, M>['outputs'],
'outputs'
>
>;
return result;
}
}
2 changes: 2 additions & 0 deletions packages/sdk/src/ColonyNetwork/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export { OneTxPayment } from './OneTxPayment.js';
export { TokenLocking } from './TokenLocking.js';
export { type Motion, VotingReputation, Vote } from './VotingReputation.js';
export * from './tokens/index.js';
export * from './CustomColonyNetwork.js';
export * from './CustomContract.js';
64 changes: 64 additions & 0 deletions packages/sdk/src/TxCreator/CustomTxCreator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
type ExtractAbiFunction,
type AbiParametersToPrimitiveTypes,
type Abi,
type ExtractAbiFunctionNames,
} from 'abitype';

import { type ContractReceipt } from 'ethers';

import { type EventData, type TxConfig } from './TxCreator.js';
import type CustomColonyNetwork from '../ColonyNetwork/CustomColonyNetwork.js';
import {
type ColonyNetwork,
type MetaTxBaseContract,
MetaTxCreator,
} from '../index.js';

/**
* Create transactions for custom contracts
*
* The `CustomTxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network.
* This TxCreator works specifically for custom contracts that are not (yet) part of the Colony SDK.
*
* ## Creating a transaction
*
* - [[CustomTxCreator.tx]]: force a Colony transaction, knowing you have the permissions to do so
* - [[CustomTxCreator.metaTx]]: same as `tx()`, but send as a gasless metatransaction
*/
class CustomTxCreator<
A extends Abi,
M extends ExtractAbiFunctionNames<A, 'payable' | 'nonpayable'>,
E extends EventData,
> extends MetaTxCreator<MetaTxBaseContract, M, E> {
constructor({
colonyNetwork,
contract,
method,
args,
eventData,
txConfig,
}: {
colonyNetwork: ColonyNetwork | CustomColonyNetwork;
contract: MetaTxBaseContract;
method: M;
args: AbiParametersToPrimitiveTypes<
ExtractAbiFunction<A, M>['inputs'],
'inputs'
>;
eventData?: (receipt: ContractReceipt) => Promise<E>;
txConfig?: TxConfig;
}) {
const customArgs = args as unknown[];
super({
colonyNetwork,
contract,
method,
args: customArgs,
eventData,
txConfig,
});
}
}

export default CustomTxCreator;
6 changes: 3 additions & 3 deletions packages/sdk/src/TxCreator/EIP2612TxCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ const { splitSignature } = utils;
interface TDSigner extends Signer, TypedDataSigner {}

/**
* An umbrella API for all kinds of transactions
* Create transactions for contracts supporting the EIP2612 standard
*
* 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]]).
* The `EIP2612TxCreator` allows for a simple API to cover all the different cases of transactions within the Colony Network.
*
* ## Create a standard transaction ("force" in dApp)
* ## Create a standard transaction ("permissions" in dApp)
*
* - [[EIP2612TxCreator.tx]]: force a Colony transaction, knowing you have the permissions to do so
* - [[EIP2612TxCreator.metaTx]]: same as `tx()`, but send as a gasless metatransaction
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/TxCreator/MetaTxCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class MetaTxCreator<
C extends MetaTxBaseContract,
M extends keyof C['functions'],
E extends EventData,
MD extends MetadataType,
MD extends MetadataType = MetadataType.DEFAULT,
> extends TxCreator<C, M, E, MD> {
protected async sendMetaTransaction(
encodedTransaction: string,
Expand Down
11 changes: 7 additions & 4 deletions packages/sdk/src/TxCreator/TxCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import {
} from '@colony/events';

import { type ParsedLogTransactionReceipt } from '../types.js';
import { type ColonyNetwork } from '../ColonyNetwork/index.js';
import {
type ColonyNetwork,
type CustomColonyNetwork,
} from '../ColonyNetwork/index.js';

export interface TxConfig<M> {
export interface TxConfig<M = MetadataType.DEFAULT> {
metadataType?: M;
}

Expand Down Expand Up @@ -158,7 +161,7 @@ export class TxCreator<
E extends EventData,
MD extends MetadataType,
> {
protected colonyNetwork: ColonyNetwork;
protected colonyNetwork: ColonyNetwork | CustomColonyNetwork;

protected contract: C;

Expand All @@ -178,7 +181,7 @@ export class TxCreator<
eventData,
txConfig,
}: {
colonyNetwork: ColonyNetwork;
colonyNetwork: ColonyNetwork | CustomColonyNetwork;
contract: C;
method: M;
args: unknown[] | (() => Promise<unknown[]>);
Expand Down
Loading

0 comments on commit 226033e

Please sign in to comment.