diff --git a/src/coinbase/smart_contract.ts b/src/coinbase/smart_contract.ts index 4b2969c6..0f439a15 100644 --- a/src/coinbase/smart_contract.ts +++ b/src/coinbase/smart_contract.ts @@ -14,9 +14,11 @@ import { NFTContractOptions, TokenContractOptions, MultiTokenContractOptions, + RegisterContractOptions, TransactionStatus, PaginationOptions, PaginationResponse, + UpdateContractOptions, } from "./types"; import { Coinbase } from "./coinbase"; import { delay } from "./utils"; @@ -105,18 +107,19 @@ export class SmartContract { /** * Register a smart contract. * - * @param networkId - The network ID. - * @param contractAddress - The contract address. - * @param abi - The ABI of the contract. - * @param contractName - The contract name. + * @param options - The options to register a smart contract. + * @param options.networkId - The network ID. + * @param options.contractAddress - The contract address. + * @param options.abi - The ABI of the contract. + * @param options.contractName - The contract name. * @returns The smart contract. */ - public static async register( - networkId: string, - contractAddress: string, - abi: object, - contractName?: string, - ): Promise { + public static async register({ + networkId, + contractAddress, + abi, + contractName, + }: RegisterContractOptions): Promise { const response = await Coinbase.apiClients.smartContract!.registerSmartContract( networkId, contractAddress, @@ -317,11 +320,12 @@ export class SmartContract { /** * Update a smart contract. * - * @param abi - The new ABI of the contract. - * @param contractName - The new contract name. + * @param options - The options to update a smart contract. + * @param options.abi - The new ABI of the contract. + * @param options.contractName - The new contract name. * @returns The smart contract. */ - public async update(abi?: object, contractName?: string): Promise { + public async update({ abi, contractName }: UpdateContractOptions): Promise { const response = await Coinbase.apiClients.smartContract!.updateSmartContract( this.getNetworkId(), this.getContractAddress(), diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 02381ac5..49f6457f 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -1284,6 +1284,24 @@ export type UpdateWebhookOptions = { eventTypeFilter?: WebhookEventTypeFilter; }; +/** + * Options for registering a smart contract. + */ +export type RegisterContractOptions = { + networkId: string; + contractAddress: string; + abi: object; + contractName?: string; +}; + +/** + * Options for updating a smart contract. + */ +export type UpdateContractOptions = { + abi?: object; + contractName?: string; +}; + /** * ContractInvocationAPI client type definition. */ diff --git a/src/tests/smart_contract_test.ts b/src/tests/smart_contract_test.ts index fbaeb3f9..e396a5bb 100644 --- a/src/tests/smart_contract_test.ts +++ b/src/tests/smart_contract_test.ts @@ -70,12 +70,12 @@ describe("SmartContract", () => { .fn() .mockResolvedValue({ data: erc20ExternalModel }); - const smartContract = await SmartContract.register( - networkId, - contractAddress, - testAllReadTypesABI, - contractName, - ); + const smartContract = await SmartContract.register({ + networkId: networkId, + contractAddress: contractAddress, + abi: testAllReadTypesABI, + contractName: contractName, + }); expect(Coinbase.apiClients.smartContract!.registerSmartContract).toHaveBeenCalledWith( networkId, @@ -94,7 +94,12 @@ describe("SmartContract", () => { .fn() .mockRejectedValue(new Error("Failed to register the smart contract")); await expect( - SmartContract.register(networkId, contractAddress, testAllReadTypesABI, contractName), + SmartContract.register({ + networkId: networkId, + contractAddress: contractAddress, + abi: testAllReadTypesABI, + contractName: contractName, + }), ).rejects.toThrow("Failed to register the smart contract"); }); }); @@ -103,21 +108,21 @@ describe("SmartContract", () => { const networkId = erc20ExternalModel.network_id; const contractAddress = erc20ExternalModel.contract_address; - const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel)); - const updatedAbiJson = { abi: "data2" }; - updatedContract.contract_name = "UpdatedContractName"; - updatedContract.abi = JSON.stringify(updatedAbiJson); - it("should update an existing smart contract", async () => { + const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel)); + const updatedAbiJson = { abi: "data2" }; + updatedContract.contract_name = "UpdatedContractName"; + updatedContract.abi = JSON.stringify(updatedAbiJson); + Coinbase.apiClients.smartContract = smartContractApiMock; Coinbase.apiClients.smartContract.updateSmartContract = jest .fn() .mockResolvedValue({ data: updatedContract }); - const smartContract = await erc20ExternalSmartContract.update( - updatedAbiJson, - updatedContract.contract_name, - ); + const smartContract = await erc20ExternalSmartContract.update({ + abi: updatedAbiJson, + contractName: updatedContract.contract_name, + }); expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith( networkId, @@ -133,12 +138,87 @@ describe("SmartContract", () => { expect(smartContract.getContractName()).toEqual(updatedContract.contract_name); }); + it("should update an existing smart contract - update contract name only", async () => { + const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel)); + updatedContract.contract_name = "UpdatedContractName"; + + Coinbase.apiClients.smartContract = smartContractApiMock; + Coinbase.apiClients.smartContract.updateSmartContract = jest + .fn() + .mockResolvedValue({ data: updatedContract }); + + const smartContract = await erc20ExternalSmartContract.update({ + contractName: updatedContract.contract_name, + }); + + expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith( + networkId, + contractAddress, + { + contract_name: updatedContract.contract_name, + abi: undefined, + }, + ); + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract.getContractAddress()).toBe(contractAddress); + expect(smartContract.getAbi()).toEqual(erc20ExternalSmartContract.getAbi()); + expect(smartContract.getContractName()).toEqual(updatedContract.contract_name); + }); + + it("should update an existing smart contract - update abi only", async () => { + const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel)); + const updatedAbiJson = { abi: "data2" }; + updatedContract.abi = JSON.stringify(updatedAbiJson); + + Coinbase.apiClients.smartContract = smartContractApiMock; + Coinbase.apiClients.smartContract.updateSmartContract = jest + .fn() + .mockResolvedValue({ data: updatedContract }); + + const smartContract = await erc20ExternalSmartContract.update({ abi: updatedAbiJson }); + + expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith( + networkId, + contractAddress, + { + contract_name: undefined, + abi: updatedContract.abi, + }, + ); + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract.getContractAddress()).toBe(contractAddress); + expect(smartContract.getAbi()).toEqual(updatedAbiJson); + expect(smartContract.getContractName()).toEqual(erc20ExternalSmartContract.getContractName()); + }); + + it("should update an existing smart contract - no update", async () => { + Coinbase.apiClients.smartContract = smartContractApiMock; + Coinbase.apiClients.smartContract.updateSmartContract = jest + .fn() + .mockResolvedValue({ data: erc20ExternalModel }); + + const smartContract = await erc20ExternalSmartContract.update({}); + + expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith( + networkId, + contractAddress, + {}, + ); + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract.getContractAddress()).toBe(contractAddress); + expect(smartContract.getAbi()).toEqual(erc20ExternalSmartContract.getAbi()); + expect(smartContract.getContractName()).toEqual(erc20ExternalSmartContract.getContractName()); + }); + it("should throw an error if update fails", async () => { Coinbase.apiClients.smartContract!.updateSmartContract = jest .fn() .mockRejectedValue(new Error("Failed to update the smart contract")); await expect( - erc20ExternalSmartContract.update(testAllReadTypesABI, updatedContract.contract_name), + erc20ExternalSmartContract.update({ + abi: testAllReadTypesABI, + contractName: erc20ExternalSmartContract.getContractName(), + }), ).rejects.toThrow("Failed to update the smart contract"); }); }); @@ -195,7 +275,7 @@ describe("SmartContract", () => { }); }); - describe('#getWalletId', () => { + describe("#getWalletId", () => { it("returns the smart contract wallet ID", () => { expect(erc20SmartContract.getWalletId()).toEqual(VALID_SMART_CONTRACT_ERC20_MODEL.wallet_id); }); @@ -474,7 +554,9 @@ describe("SmartContract", () => { }); it("throws an error when the smart contract is external", async () => { - expect(externalSmartContract.reload()).rejects.toThrow("Cannot reload an external SmartContract"); + expect(externalSmartContract.reload()).rejects.toThrow( + "Cannot reload an external SmartContract", + ); }); }); @@ -482,8 +564,8 @@ describe("SmartContract", () => { it("returns the same value as toString", () => { expect(erc20SmartContract.toString()).toEqual( `SmartContract{id: '${erc20SmartContract.getId()}', networkId: '${erc20SmartContract.getNetworkId()}', ` + - `contractAddress: '${erc20SmartContract.getContractAddress()}', deployerAddress: '${erc20SmartContract.getDeployerAddress()}', ` + - `type: '${erc20SmartContract.getType()}'}`, + `contractAddress: '${erc20SmartContract.getContractAddress()}', deployerAddress: '${erc20SmartContract.getDeployerAddress()}', ` + + `type: '${erc20SmartContract.getType()}'}`, ); }); });