Skip to content

Commit

Permalink
[SDK] Multichain registry (#311)
Browse files Browse the repository at this point in the history
Co-authored-by: Yash <kumaryashcse@gmail.com>
  • Loading branch information
joaquim-verges and kumaryash90 committed Jan 6, 2023
1 parent 557429b commit 9eaa21d
Show file tree
Hide file tree
Showing 18 changed files with 358 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/moody-falcons-sell.md
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/contracts-js": patch
---

Update to v3.3.0 of contracts package
5 changes: 5 additions & 0 deletions .changeset/shy-apes-jam.md
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/sdk": patch
---

Add support for multi chain registry
5 changes: 5 additions & 0 deletions .changeset/tricky-students-explode.md
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/contracts-js": patch
---

Update to 3.1.10 of contracts package
@@ -0,0 +1,4 @@
{
"main": "dist/thirdweb-dev-contracts-js-factories-TWRegistryLegacy__factory.cjs.js",
"module": "dist/thirdweb-dev-contracts-js-factories-TWRegistryLegacy__factory.esm.js"
}
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Expand Up @@ -62,7 +62,7 @@
"build": "tsc && preconstruct build",
"test:evm:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000 --parallel './test/evm/**/*.test.ts'",
"test:evm": "make test-evm",
"test:evm:single": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000",
"test:evm:single": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 90000",
"node:solana:start": "DEBUG='amman:(info|error|debug)' amman start --forceClone",
"node:solana:stop": "amman stop",
"test:solana:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/solana/.mocharc.json' --timeout 30000 --parallel './test/solana/**/*.test.ts'",
Expand Down
14 changes: 14 additions & 0 deletions packages/sdk/src/evm/constants/addresses.ts
Expand Up @@ -11,6 +11,7 @@ export const OZ_DEFENDER_FORWARDER_ADDRESS =
const TWRegistry_address = "0x7c487845f98938Bb955B1D5AD069d9a30e4131fd";
const TWFactory_address = "0x5DBC7B840baa9daBcBe9D2492E45D7244B54A2A0";
const ContractPublisher_address = "0x664244560eBa21Bf82d7150C791bE1AbcD5B4cd7"; // Polygon only
const MultichainRegistry_address = "0xcdAD8FA86e18538aC207872E8ff3536501431B73"; // Polygon only

/**
* @internal
Expand Down Expand Up @@ -291,6 +292,19 @@ export function getContractPublisherAddress() {
}
}

/**
* @internal
*/
export function getMultichainRegistryAddress() {
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (process.env.multiChainRegistryAddress) {
// eslint-disable-next-line turbo/no-undeclared-env-vars
return process.env.multiChainRegistryAddress as string;
} else {
return MultichainRegistry_address;
}
}

/**
*
* @param chainId - chain id
Expand Down
18 changes: 18 additions & 0 deletions packages/sdk/src/evm/constants/urls.ts
Expand Up @@ -168,3 +168,21 @@ export function getReadOnlyProvider(network: string, chainId?: number) {
return ethers.getDefaultProvider(network);
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ChainIdToName: Record<number, string> = {
[ChainId.Polygon]: "polygon",
[ChainId.Mumbai]: "mumbai",
[ChainId.Goerli]: "goerli",
[ChainId.Mainnet]: "mainnet",
[ChainId.Optimism]: "optimism",
[ChainId.OptimismGoerli]: "optimism-goerli",
[ChainId.Arbitrum]: "arbitrum",
[ChainId.ArbitrumGoerli]: "arbitrum-goerli",
[ChainId.Fantom]: "fantom",
[ChainId.FantomTestnet]: "fantom-testnet",
[ChainId.Avalanche]: "avalanche",
[ChainId.AvalancheFujiTestnet]: "avalanche-fuji",
[ChainId.BinanceSmartChainMainnet]: "binance",
[ChainId.BinanceSmartChainTestnet]: "binance-testnet",
};
141 changes: 141 additions & 0 deletions packages/sdk/src/evm/core/classes/multichain-registry.ts
@@ -0,0 +1,141 @@
import { NetworkOrSignerOrProvider, TransactionResult } from "..";
import { getMultichainRegistryAddress } from "../../constants/addresses";
import { PublishedMetadata } from "../../schema/contracts/custom";
import { SDKOptions } from "../../schema/sdk-options";
import { AddContractInput, ContractInput, DeployedContract } from "../../types";
import { ContractWrapper } from "./contract-wrapper";
import type {
TWMultichainRegistryRouter,
TWMultichainRegistryLogic,
} from "@thirdweb-dev/contracts-js";
import TWRegistryABI from "@thirdweb-dev/contracts-js/dist/abis/TWMultichainRegistryLogic.json";
import TWRegistryRouterABI from "@thirdweb-dev/contracts-js/dist/abis/TWMultichainRegistryRouter.json";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { constants, utils } from "ethers";

/**
* @internal
*/
export class MultichainRegistry {
private registryLogic: ContractWrapper<TWMultichainRegistryLogic>;
private registryRouter: ContractWrapper<TWMultichainRegistryRouter>;
private storage: ThirdwebStorage;

constructor(
network: NetworkOrSignerOrProvider,
storage: ThirdwebStorage,
options: SDKOptions = {},
) {
this.storage = storage;
this.registryLogic = new ContractWrapper<TWMultichainRegistryLogic>(
network,
getMultichainRegistryAddress(),
TWRegistryABI,
options,
);

this.registryRouter = new ContractWrapper<TWMultichainRegistryRouter>(
network,
getMultichainRegistryAddress(),
TWRegistryRouterABI,
options,
);
}

public async updateSigner(signer: NetworkOrSignerOrProvider) {
this.registryLogic.updateSignerOrProvider(signer);
this.registryRouter.updateSignerOrProvider(signer);
}

public async getContractMetadataURI(
chainId: number,
address: string,
): Promise<string> {
return await this.registryLogic.readContract.getMetadataUri(
chainId,
address,
);
}

public async getContractMetadata(
chainId: number,
address: string,
): Promise<PublishedMetadata> {
const uri = await this.getContractMetadataURI(chainId, address);
if (!uri) {
throw new Error(
`No metadata URI found for contract ${address} on chain ${chainId}`,
);
}
// TODO define the metadata JSON schema
return await this.storage.downloadJSON<PublishedMetadata>(uri);
}

public async getContractAddresses(
walletAddress: string,
): Promise<DeployedContract[]> {
return (await this.registryLogic.readContract.getAll(walletAddress))
.filter(
(result) =>
utils.isAddress(result.deploymentAddress) &&
result.deploymentAddress.toLowerCase() !== constants.AddressZero,
)
.map((result) => ({
address: result.deploymentAddress,
chainId: result.chainId.toNumber(),
}));
}

public async addContract(
contract: AddContractInput,
): Promise<TransactionResult> {
return await this.addContracts([contract]);
}

public async addContracts(
contracts: AddContractInput[],
): Promise<TransactionResult> {
const deployerAddress = await this.registryRouter.getSignerAddress();
const encoded: string[] = [];
contracts.forEach((contact) => {
encoded.push(
this.registryLogic.readContract.interface.encodeFunctionData("add", [
deployerAddress,
contact.address,
contact.chainId,
contact.metadataURI || "",
]),
);
});

return {
receipt: await this.registryRouter.multiCall(encoded),
};
}

public async removeContract(
contract: ContractInput,
): Promise<TransactionResult> {
return await this.removeContracts([contract]);
}

public async removeContracts(
contracts: ContractInput[],
): Promise<TransactionResult> {
const deployerAddress = await this.registryRouter.getSignerAddress();
const encoded: string[] = [];
contracts.forEach((contract) => {
encoded.push(
this.registryLogic.readContract.interface.encodeFunctionData("remove", [
deployerAddress,
contract.address,
contract.chainId,
]),
);
});

return {
receipt: await this.registryRouter.multiCall(encoded),
};
}
}
14 changes: 13 additions & 1 deletion packages/sdk/src/evm/core/sdk.ts
Expand Up @@ -17,6 +17,7 @@ import { WalletAuthenticator } from "./auth/wallet-authenticator";
import type { ContractMetadata } from "./classes";
import { ContractDeployer } from "./classes/contract-deployer";
import { ContractPublisher } from "./classes/contract-publisher";
import { MultichainRegistry } from "./classes/multichain-registry";
import {
getSignerAndProvider,
RPCConnectionHandler,
Expand Down Expand Up @@ -162,6 +163,10 @@ export class ThirdwebSDK extends RPCConnectionHandler {
* New contract deployer
*/
public deployer: ContractDeployer;
/**
* The registry of deployed contracts
*/
public multiChainRegistry: MultichainRegistry;
/**
* Interact with the connected wallet
*/
Expand All @@ -187,6 +192,11 @@ export class ThirdwebSDK extends RPCConnectionHandler {
this.wallet = new UserWallet(signerOrProvider, options);
this.deployer = new ContractDeployer(signerOrProvider, options, storage);
this.auth = new WalletAuthenticator(signerOrProvider, this.wallet, options);
this.multiChainRegistry = new MultichainRegistry(
signerOrProvider,
this.storageHandler,
this.options,
);
this._publisher = new ContractPublisher(
signerOrProvider,
this.options,
Expand Down Expand Up @@ -425,7 +435,7 @@ export class ThirdwebSDK extends RPCConnectionHandler {
if (!contractTypeOrABI || contractTypeOrABI === "custom") {
const resolvedContractType = await this.resolveContractType(address);
if (resolvedContractType === "custom") {
// if it's a custom contract we gotta fetch the compilet metadata
// if it's a custom contract we gotta fetch the compiler metadata
try {
const publisher = this.getPublisher();
const metadata = await publisher.fetchCompilerMetadataFromAddress(
Expand Down Expand Up @@ -515,6 +525,7 @@ export class ThirdwebSDK extends RPCConnectionHandler {
* ```
*/
public async getContractList(walletAddress: string) {
// TODO - this only reads from the current registry chain, not the multichain registry
const addresses =
(await (
await this.deployer.getRegistry()
Expand Down Expand Up @@ -574,6 +585,7 @@ export class ThirdwebSDK extends RPCConnectionHandler {
this.auth.updateSignerOrProvider(this.getSignerOrProvider());
this.deployer.updateSignerOrProvider(this.getSignerOrProvider());
this._publisher.updateSignerOrProvider(this.getSignerOrProvider());
this.multiChainRegistry.updateSigner(this.getSignerOrProvider());
for (const [, contract] of this.contractCache) {
contract.onNetworkUpdated(this.getSignerOrProvider());
}
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/src/evm/schema/contracts/common/plugins.ts
@@ -0,0 +1,12 @@
import { BytesLikeSchema } from "../../../../core/schema/shared";
import { AddressSchema } from "../../shared";
import { z } from "zod";

/**
* @internal
*/
export const PluginMapInput = z.object({
functionSelector: BytesLikeSchema,
functionSignature: z.string(),
pluginAddress: AddressSchema,
});
1 change: 1 addition & 0 deletions packages/sdk/src/evm/types/index.ts
Expand Up @@ -8,3 +8,4 @@ export * from "./SplitRecipient";
export * from "./deploy";
export * from "./events";
export * from "./multiwrap";
export * from "./registry";
8 changes: 8 additions & 0 deletions packages/sdk/src/evm/types/plugins.ts
@@ -0,0 +1,8 @@
import { PluginMapInput } from "../schema/contracts/common/plugins";
import { z } from "zod";

/**
* Input model to pass a list of extension-addresses + function-selectors
* @public
*/
export type Plugin = z.input<typeof PluginMapInput>;
13 changes: 13 additions & 0 deletions packages/sdk/src/evm/types/registry.ts
@@ -0,0 +1,13 @@
export type ContractInput = {
address: string;
chainId: number;
};

export type AddContractInput = ContractInput & {
metadataURI?: string;
};

export type DeployedContract = {
address: string;
chainId: number;
};

0 comments on commit 9eaa21d

Please sign in to comment.