diff --git a/src/common.ts b/src/common.ts index 3427797d..d4e2c3b0 100644 --- a/src/common.ts +++ b/src/common.ts @@ -39,16 +39,14 @@ export type WalletAddress = string; // TODO: append this with other configuration options (e.g. local vs. remote evaluation) export type ContractSigner = ArweaveSigner | ArconnectSigner; -export type ContractConfiguration = { - signer?: ContractSigner; // TODO: optionally allow JWK in place of signer -} & ( +export type WithSigner = { signer: ContractSigner }; // TODO: optionally allow JWK in place of signer +export type ContractConfiguration = | { contract?: WarpContract | RemoteContract; } | { contractTxId: string; - } -); + }; export function isContractConfiguration( config: ContractConfiguration, diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 97eb579e..590df92c 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -33,27 +33,76 @@ import { RegistrationType, UpdateGatewaySettingsParams, WeightedObserver, + WithSigner, WriteInteractionResult, isContractConfiguration, isContractTxIdConfiguration, } from '../types.js'; -import { mixInto } from '../utils/common.js'; import { RemoteContract } from './contracts/remote-contract.js'; -import { InvalidSignerError, WarpContract } from './index.js'; +import { WarpContract } from './index.js'; -export class ArIO implements ArIOReadContract { - private contract: RemoteContract | WarpContract; - private signer: ContractSigner | undefined; +export class ArIO { + static createContract( + config: ContractConfiguration, + ): WarpContract { + if (isContractConfiguration(config)) { + if (config.contract instanceof WarpContract) { + return config.contract; + } else { + // TODO: throw error if contract is not of WarpContract type + return new WarpContract(config.contract.configuration()); + } + } else if (isContractTxIdConfiguration(config)) { + return new WarpContract({ contractTxId: config.contractTxId }); + } else { + throw new Error('Invalid configuration.'); + } + } + + /** + * Initializes an ArIO instance. + * + * There are two overloads for this function: + * 1. When a signer is provided in the configuration, it returns an instance of ArIOWritable. + * 2. When a signer is not provided in the configuration, it returns an instance of ArIOReadable. + * + * + * @param {ContractConfiguration & WithSigner} config - The configuration object. + * If a signer is provided, it should be an object that implements the ContractSigner interface. + * + * @returns {ArIOWritable | ArIOReadable} - An instance of ArIOWritable if a signer is provided, otherwise an instance of ArIOReadable. + * @throws {Error} - Throws an error if the configuration is invalid. + * + * @example + * // Overload 1: When signer is provided + * const writable = ArIO.init({ signer: mySigner, contract: myContract }); + * + * // Overload 2: When signer is not provided + * const readable = ArIO.init({ contract: myContract }); + */ + static init(config: ContractConfiguration & WithSigner): ArIOWritable; + static init(config: ContractConfiguration): ArIOReadable; + static init(config: ContractConfiguration & { signer?: ContractSigner }) { + if (config.signer) { + const signer = config.signer; + const contract = this.createContract(config); + return new ArIOWritable({ signer, contract }); + } else { + return new ArIOReadable(config); + } + } +} + +export class ArIOReadable implements ArIOReadContract { + protected contract: RemoteContract | WarpContract; constructor( - { signer, ...config }: ContractConfiguration = { + config: ContractConfiguration = { contract: new RemoteContract({ contractTxId: ARNS_TESTNET_REGISTRY_TX, }), }, ) { - this.signer = signer; - if (isContractConfiguration(config)) { this.contract = config.contract; } else if (isContractTxIdConfiguration(config)) { @@ -65,10 +114,6 @@ export class ArIO implements ArIOReadContract { } } - connected(): boolean { - return this.signer !== undefined; - } - /** * Returns the current state of the contract. */ @@ -243,42 +288,18 @@ export class ArIO implements ArIOReadContract { return auctions; } - - connect(signer: ContractSigner): void { - let writeableContract: WarpContract | undefined = undefined; - - // create or set the writable contract to be used in our mixin class - if (this.contract instanceof RemoteContract) { - writeableContract = new WarpContract(this.contract.configuration()); - } else if (this.contract instanceof WarpContract) { - writeableContract = this.contract; - } - - if (!writeableContract) { - throw new InvalidSignerError(); - } - - mixInto( - this.contract, - new ArIOWritable({ - contract: writeableContract, - signer, - }), - ); - } } -export class ArIOWritable implements ArIOWriteContract { - private contract: WarpContract; +export class ArIOWritable extends ArIOReadable implements ArIOWriteContract { + protected declare contract: WarpContract; private signer: ContractSigner; constructor({ contract, signer, }: { contract: WarpContract; - signer: ContractSigner; - }) { - this.contract = contract; + } & WithSigner) { + super({ contract }); this.signer = signer; } diff --git a/src/utils/common.ts b/src/utils/common.ts deleted file mode 100644 index ed849fc1..00000000 --- a/src/utils/common.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -export function mixInto(targetInstance: object, mixinInstance: object) { - Object.getOwnPropertyNames(mixinInstance.constructor.prototype) - .filter((prop) => prop !== 'constructor' && !prop.startsWith('#')) - .forEach((method) => { - targetInstance[method] = mixinInstance[method].bind(mixinInstance); - }); -} diff --git a/src/utils/index.ts b/src/utils/index.ts index 8715da0f..0738d3ef 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -17,4 +17,3 @@ export * from './arweave.js'; export * from './http-client.js'; export * from './smartweave.js'; -export * from './common.js'; diff --git a/tests/integration/ar-io.test.ts b/tests/integration/ar-io.test.ts index de84088d..fee5fcf0 100644 --- a/tests/integration/ar-io.test.ts +++ b/tests/integration/ar-io.test.ts @@ -16,7 +16,7 @@ const contractTxId = ARNS_DEVNET_REGISTRY_TX; const localCacheUrl = `https://api.arns.app`; describe('ArIO Client', () => { const signer = new ArweaveSigner(JSON.parse(process.env.PRIMARY_WALLET_JWK!)); - const arIO = new ArIO({ + const arIO = ArIO.init({ signer, contract: new RemoteContract({ cacheUrl: localCacheUrl, @@ -30,13 +30,13 @@ describe('ArIO Client', () => { }); it('should connect and return a valid instance', async () => { - const client = new ArIO({ + const client = ArIO.init({ contract: new RemoteContract({ contractTxId, cacheUrl: localCacheUrl, }), }); - expect(client.connect(signer)).toBeDefined(); + expect(client).toBeDefined(); expect(client).toBeInstanceOf(ArIO); });