From 41764c2b57c62a8601760c71b0c50b86046b1aa0 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 15 Jan 2025 01:07:44 -0500 Subject: [PATCH 1/8] Working end to end --- src/client/api.ts | 160 ++++++++++++++++++++++++- src/coinbase/address/wallet_address.ts | 66 ++++++++++ src/coinbase/types.ts | 24 ++++ 3 files changed, 249 insertions(+), 1 deletion(-) diff --git a/src/client/api.ts b/src/client/api.ts index c151c71d..22b45a0b 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -422,6 +422,68 @@ export interface BuildStakingOperationRequest { */ 'options': { [key: string]: string; }; } +/** + * + * @export + * @interface CompileSmartContractRequest + */ +export interface CompileSmartContractRequest { + /** + * The JSON input containing the Solidity code, dependencies, and compiler settings. + * @type {string} + * @memberof CompileSmartContractRequest + */ + 'solidity_input_json': string; + /** + * The name of the contract to compile. + * @type {string} + * @memberof CompileSmartContractRequest + */ + 'contract_name': string; + /** + * The version of the Solidity compiler to use. + * @type {string} + * @memberof CompileSmartContractRequest + */ + 'solidity_compiler_version': string; +} +/** + * Represents a compiled smart contract that can be deployed onchain + * @export + * @interface CompiledSmartContract + */ +export interface CompiledSmartContract { + /** + * The unique identifier of the compiled smart contract. + * @type {string} + * @memberof CompiledSmartContract + */ + 'compiled_smart_contract_id'?: string; + /** + * The JSON-encoded input for the Solidity compiler + * @type {string} + * @memberof CompiledSmartContract + */ + 'solidity_input_json'?: string; + /** + * The contract creation bytecode which will be used with constructor arguments to deploy the contract + * @type {string} + * @memberof CompiledSmartContract + */ + 'contract_creation_bytecode'?: string; + /** + * The JSON-encoded ABI of the contract + * @type {string} + * @memberof CompiledSmartContract + */ + 'abi'?: string; + /** + * The name of the smart contract to deploy + * @type {string} + * @memberof CompiledSmartContract + */ + 'contract_name'?: string; +} /** * Represents a single decoded event emitted by a smart contract * @export @@ -835,6 +897,12 @@ export interface CreateSmartContractRequest { * @memberof CreateSmartContractRequest */ 'options': SmartContractOptions; + /** + * The UUID of the compiled smart contract to deploy. + * @type {string} + * @memberof CreateSmartContractRequest + */ + 'compiled_smart_contract_id'?: string; } @@ -2766,6 +2834,12 @@ export interface SmartContract { * @memberof SmartContract */ 'is_external': boolean; + /** + * The ID of the compiled smart contract that was used to deploy this contract + * @type {string} + * @memberof SmartContract + */ + 'compiled_smart_contract_id'?: string; } @@ -2914,7 +2988,7 @@ export interface SmartContractList { * Options for smart contract creation * @export */ -export type SmartContractOptions = MultiTokenContractOptions | NFTContractOptions | TokenContractOptions; +export type SmartContractOptions = MultiTokenContractOptions | NFTContractOptions | TokenContractOptions | { [key: string]: any; }; /** * The type of the smart contract. @@ -8649,6 +8723,45 @@ export class ServerSignersApi extends BaseAPI implements ServerSignersApiInterfa */ export const SmartContractsApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * Compile a smart contract + * @summary Compile a smart contract + * @param {CompileSmartContractRequest} compileSmartContractRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + compileSmartContract: async (compileSmartContractRequest: CompileSmartContractRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'compileSmartContractRequest' is not null or undefined + assertParamExists('compileSmartContract', 'compileSmartContractRequest', compileSmartContractRequest) + const localVarPath = `/v1/smart_contracts/compile`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(compileSmartContractRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Create a new smart contract * @summary Create a new smart contract @@ -8992,6 +9105,19 @@ export const SmartContractsApiAxiosParamCreator = function (configuration?: Conf export const SmartContractsApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = SmartContractsApiAxiosParamCreator(configuration) return { + /** + * Compile a smart contract + * @summary Compile a smart contract + * @param {CompileSmartContractRequest} compileSmartContractRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async compileSmartContract(compileSmartContractRequest: CompileSmartContractRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.compileSmartContract(compileSmartContractRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartContractsApi.compileSmartContract']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Create a new smart contract * @summary Create a new smart contract @@ -9106,6 +9232,16 @@ export const SmartContractsApiFp = function(configuration?: Configuration) { export const SmartContractsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = SmartContractsApiFp(configuration) return { + /** + * Compile a smart contract + * @summary Compile a smart contract + * @param {CompileSmartContractRequest} compileSmartContractRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + compileSmartContract(compileSmartContractRequest: CompileSmartContractRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.compileSmartContract(compileSmartContractRequest, options).then((request) => request(axios, basePath)); + }, /** * Create a new smart contract * @summary Create a new smart contract @@ -9198,6 +9334,16 @@ export const SmartContractsApiFactory = function (configuration?: Configuration, * @interface SmartContractsApi */ export interface SmartContractsApiInterface { + /** + * Compile a smart contract + * @summary Compile a smart contract + * @param {CompileSmartContractRequest} compileSmartContractRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartContractsApiInterface + */ + compileSmartContract(compileSmartContractRequest: CompileSmartContractRequest, options?: RawAxiosRequestConfig): AxiosPromise; + /** * Create a new smart contract * @summary Create a new smart contract @@ -9290,6 +9436,18 @@ export interface SmartContractsApiInterface { * @extends {BaseAPI} */ export class SmartContractsApi extends BaseAPI implements SmartContractsApiInterface { + /** + * Compile a smart contract + * @summary Compile a smart contract + * @param {CompileSmartContractRequest} compileSmartContractRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartContractsApi + */ + public compileSmartContract(compileSmartContractRequest: CompileSmartContractRequest, options?: RawAxiosRequestConfig) { + return SmartContractsApiFp(this.configuration).compileSmartContract(compileSmartContractRequest, options).then((request) => request(this.axios, this.basePath)); + } + /** * Create a new smart contract * @summary Create a new smart contract diff --git a/src/coinbase/address/wallet_address.ts b/src/coinbase/address/wallet_address.ts index 47d851ba..e79f8251 100644 --- a/src/coinbase/address/wallet_address.ts +++ b/src/coinbase/address/wallet_address.ts @@ -22,6 +22,7 @@ import { PaginationResponse, CreateFundOptions, CreateQuoteOptions, + CreateCustomContractOptions, } from "../types"; import { delay } from "../utils"; import { Wallet as WalletClass } from "../wallet"; @@ -448,6 +449,34 @@ export class WalletAddress extends Address { return smartContract; } + + /** + * Deploys a custom contract. + * + * @param options - The options for creating the custom contract. + * @param options.solidityVersion - The version of the solidity compiler, must be 0.8.+, such as "0.8.28+commit.7893614a". See https://binaries.soliditylang.org/bin/list.json + * @param options.solidityInputJson - The input json for the solidity compiler. See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description for more details. + * @param options.contractName - The name of the contract class. + * @param options.constructorArgs - The arguments for the constructor. + * @returns A Promise that resolves to the deployed SmartContract object. + * @throws {APIError} If the API request to create a smart contract fails. + */ + public async deployContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }: CreateCustomContractOptions): Promise { + if (!Coinbase.useServerSigner && !this.key) { + throw new Error("Cannot deploy custom contract without private key loaded"); + } + + const smartContract = await this.createCustomContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }); + + if (Coinbase.useServerSigner) { + return smartContract; + } + + await smartContract.sign(this.getSigner()); + await smartContract.broadcast(); + + return smartContract; + } /** * Creates an ERC20 token contract. @@ -525,6 +554,43 @@ export class WalletAddress extends Address { return SmartContract.fromModel(resp?.data); } + /** + * Creates a custom contract. + * + * @private + * @param {CreateCustomContractOptions} options - The options for creating the custom contract. + * @param {string} options.solidityVersion - The version of the solidity compiler, must be 0.8.+, such as "0.8.28+commit.7893614a". See https://binaries.soliditylang.org/bin/list.json + * @param {string} options.solidityInputJson - The input json for the solidity compiler. See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description for more details. + * @param {string} options.contractName - The name of the contract class. + * @param {Record} options.constructorArgs - The arguments for the constructor. + * @returns {Promise} A Promise that resolves to the created SmartContract. + * @throws {APIError} If the API request to create a smart contract fails. + */ + private async createCustomContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }: CreateCustomContractOptions): Promise { + const compileContractResp = await Coinbase.apiClients.smartContract!.compileSmartContract( + { + solidity_compiler_version: solidityVersion, + solidity_input_json: solidityInputJson, + contract_name: contractName, + }, + ); + + const compiledContract = compileContractResp.data; + const compiledContractId = compiledContract.compiled_smart_contract_id; + + const createContractResp = await Coinbase.apiClients.smartContract!.createSmartContract( + this.getWalletId(), + this.getId(), + { + type: SmartContractType.Custom, + options: constructorArgs, + compiled_smart_contract_id: compiledContractId, + }, + ); + return SmartContract.fromModel(createContractResp?.data); + } + + /** * Creates a contract invocation with the given data. * diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 0903f7e7..4a546a9f 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -64,6 +64,8 @@ import { AddressReputation, RegisterSmartContractRequest, UpdateSmartContractRequest, + CompileSmartContractRequest, + CompiledSmartContract, } from "./../client/api"; import { Address } from "./address"; import { Wallet } from "./wallet"; @@ -1134,6 +1136,16 @@ export type CreateERC1155Options = { uri: string; }; +/** + * Options for creating an arbitrary contract. + */ +export type CreateCustomContractOptions = { + solidityVersion: string; + solidityInputJson: string; + contractName: string; + constructorArgs: Record +}; + /** * Options for creating a fund operation. */ @@ -1468,6 +1480,18 @@ export interface SmartContractAPIClient { options?: RawAxiosRequestConfig, ): AxiosPromise; + /** + * Compiles a custom contract. + * @param compileSmartContractRequest - The request body containing the compile smart contract details. + * @param options - Axios request options. + * @returns - A promise resolving to the compiled smart contract. + * @throws {APIError} If the request fails. + */ + compileSmartContract( + compileSmartContractRequest: CompileSmartContractRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise; + /** * Creates a new Smart Contract. * From 9348b47ecab8e9d04540a55958e202b6d48c6f5b Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 00:28:48 -0500 Subject: [PATCH 2/8] Moreee --- src/client/api.ts | 4 ++-- src/coinbase/address/wallet_address.ts | 7 +++++-- src/coinbase/smart_contract.ts | 12 +++++++++++- src/coinbase/types.ts | 3 ++- src/coinbase/wallet.ts | 16 ++++++++++++++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/client/api.ts b/src/client/api.ts index 22b45a0b..9e380bf3 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -898,7 +898,7 @@ export interface CreateSmartContractRequest { */ 'options': SmartContractOptions; /** - * The UUID of the compiled smart contract to deploy. + * The optional UUID of the compiled smart contract to deploy. This field is only required when SmartContractType is set to custom. * @type {string} * @memberof CreateSmartContractRequest */ @@ -2988,7 +2988,7 @@ export interface SmartContractList { * Options for smart contract creation * @export */ -export type SmartContractOptions = MultiTokenContractOptions | NFTContractOptions | TokenContractOptions | { [key: string]: any; }; +export type SmartContractOptions = MultiTokenContractOptions | NFTContractOptions | TokenContractOptions | string; /** * The type of the smart contract. diff --git a/src/coinbase/address/wallet_address.ts b/src/coinbase/address/wallet_address.ts index e79f8251..bf6a35c4 100644 --- a/src/coinbase/address/wallet_address.ts +++ b/src/coinbase/address/wallet_address.ts @@ -554,7 +554,7 @@ export class WalletAddress extends Address { return SmartContract.fromModel(resp?.data); } - /** + /** * Creates a custom contract. * * @private @@ -575,6 +575,9 @@ export class WalletAddress extends Address { }, ); + console.log("compileContractResp", compileContractResp) + // TODO - add logic to bail here with errors if any + const compiledContract = compileContractResp.data; const compiledContractId = compiledContract.compiled_smart_contract_id; @@ -583,7 +586,7 @@ export class WalletAddress extends Address { this.getId(), { type: SmartContractType.Custom, - options: constructorArgs, + options: JSON.stringify(constructorArgs), compiled_smart_contract_id: compiledContractId, }, ); diff --git a/src/coinbase/smart_contract.ts b/src/coinbase/smart_contract.ts index 0f439a15..af5bccf0 100644 --- a/src/coinbase/smart_contract.ts +++ b/src/coinbase/smart_contract.ts @@ -6,6 +6,7 @@ import { SmartContractOptions as SmartContractOptionsModel, TokenContractOptions as TokenContractOptionsModel, NFTContractOptions as NFTContractOptionsModel, + MultiTokenContractOptions as MultiTokenContractOptionsModel, } from "../client/api"; import { Transaction } from "./transaction"; import { @@ -277,10 +278,12 @@ export class SmartContract { symbol: options.symbol, baseURI: options.base_uri, } as NFTContractOptions; - } else { + } else if (this.isERC1155(this.getType(), options)) { return { uri: options.uri, } as MultiTokenContractOptions; + } else { + return options as string; } } @@ -451,4 +454,11 @@ export class SmartContract { ): options is NFTContractOptionsModel { return type === SmartContractType.ERC721; } + + private isERC1155( + type: SmartContractType, + options: SmartContractOptionsModel, + ): options is MultiTokenContractOptionsModel { + return type === SmartContractType.ERC1155; + } } diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 4a546a9f..810560e9 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -1077,7 +1077,8 @@ export type MultiTokenContractOptions = { export type SmartContractOptions = | NFTContractOptions | TokenContractOptions - | MultiTokenContractOptions; + | MultiTokenContractOptions + | string; /** * Options for creating a Transfer. diff --git a/src/coinbase/wallet.ts b/src/coinbase/wallet.ts index b23f3c3f..4834d668 100644 --- a/src/coinbase/wallet.ts +++ b/src/coinbase/wallet.ts @@ -39,6 +39,7 @@ import { PaginationResponse, CreateFundOptions, CreateQuoteOptions, + CreateCustomContractOptions, } from "./types"; import { convertStringToHex, delay, formatDate, getWeekBackDate } from "./utils"; import { StakingOperation } from "./staking_operation"; @@ -939,6 +940,21 @@ export class Wallet { return (await this.getDefaultAddress()).deployMultiToken(options); } + /** + * Deploys a custom contract. + * + * @param options - The options for creating the custom contract. + * @param options.solidityVersion - The version of the solidity compiler, must be 0.8.+, such as "0.8.28+commit.7893614a". See https://binaries.soliditylang.org/bin/list.json + * @param options.solidityInputJson - The input json for the solidity compiler. See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description for more details. + * @param options.contractName - The name of the contract class. + * @param options.constructorArgs - The arguments for the constructor. + * @returns A Promise that resolves to the deployed SmartContract object. + * @throws {Error} If the private key is not loaded when not using server signer. + */ + public async deployContract(options: CreateCustomContractOptions): Promise { + return (await this.getDefaultAddress()).deployContract(options); + } + /** * Fund the wallet from your account on the Coinbase Platform. * From a8e380bd208a41f46332564f14d52be210fbdeed Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 00:32:33 -0500 Subject: [PATCH 3/8] changes' --- src/coinbase/address/wallet_address.ts | 9 +++------ src/coinbase/wallet.ts | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/coinbase/address/wallet_address.ts b/src/coinbase/address/wallet_address.ts index bf6a35c4..c3aaa06a 100644 --- a/src/coinbase/address/wallet_address.ts +++ b/src/coinbase/address/wallet_address.ts @@ -456,7 +456,7 @@ export class WalletAddress extends Address { * @param options - The options for creating the custom contract. * @param options.solidityVersion - The version of the solidity compiler, must be 0.8.+, such as "0.8.28+commit.7893614a". See https://binaries.soliditylang.org/bin/list.json * @param options.solidityInputJson - The input json for the solidity compiler. See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description for more details. - * @param options.contractName - The name of the contract class. + * @param options.contractName - The name of the contract class to be deployed. * @param options.constructorArgs - The arguments for the constructor. * @returns A Promise that resolves to the deployed SmartContract object. * @throws {APIError} If the API request to create a smart contract fails. @@ -564,7 +564,7 @@ export class WalletAddress extends Address { * @param {string} options.contractName - The name of the contract class. * @param {Record} options.constructorArgs - The arguments for the constructor. * @returns {Promise} A Promise that resolves to the created SmartContract. - * @throws {APIError} If the API request to create a smart contract fails. + * @throws {APIError} If the API request to compile or subsequently create a smart contract fails. */ private async createCustomContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }: CreateCustomContractOptions): Promise { const compileContractResp = await Coinbase.apiClients.smartContract!.compileSmartContract( @@ -575,9 +575,6 @@ export class WalletAddress extends Address { }, ); - console.log("compileContractResp", compileContractResp) - // TODO - add logic to bail here with errors if any - const compiledContract = compileContractResp.data; const compiledContractId = compiledContract.compiled_smart_contract_id; @@ -592,7 +589,7 @@ export class WalletAddress extends Address { ); return SmartContract.fromModel(createContractResp?.data); } - + /** * Creates a contract invocation with the given data. diff --git a/src/coinbase/wallet.ts b/src/coinbase/wallet.ts index 4834d668..3626b434 100644 --- a/src/coinbase/wallet.ts +++ b/src/coinbase/wallet.ts @@ -946,7 +946,7 @@ export class Wallet { * @param options - The options for creating the custom contract. * @param options.solidityVersion - The version of the solidity compiler, must be 0.8.+, such as "0.8.28+commit.7893614a". See https://binaries.soliditylang.org/bin/list.json * @param options.solidityInputJson - The input json for the solidity compiler. See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description for more details. - * @param options.contractName - The name of the contract class. + * @param options.contractName - The name of the contract class to be deployed. * @param options.constructorArgs - The arguments for the constructor. * @returns A Promise that resolves to the deployed SmartContract object. * @throws {Error} If the private key is not loaded when not using server signer. From 15eb0f39bdfb418c5caa0d6815af15df4b7e9f99 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 00:56:44 -0500 Subject: [PATCH 4/8] Tests --- src/tests/utils.ts | 27 +++ src/tests/wallet_address_test.ts | 280 +++++++++++++++++++++++++++++++ src/tests/wallet_test.ts | 55 ++++++ 3 files changed, 362 insertions(+) diff --git a/src/tests/utils.ts b/src/tests/utils.ts index cd0c9c9d..337d84ad 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -13,6 +13,7 @@ import { FaucetTransaction as FaucetTransactionModel, StakingOperation as StakingOperationModel, PayloadSignature as PayloadSignatureModel, + CompiledSmartContract as CompiledSmartContractModel, PayloadSignatureList, PayloadSignatureStatusEnum, ContractInvocation as ContractInvocationModel, @@ -390,6 +391,31 @@ export const VALID_SMART_CONTRACT_ERC1155_MODEL: SmartContractModel = { }, }; +export const VALID_COMPILED_CONTRACT_MODEL: CompiledSmartContractModel = { + compiled_smart_contract_id: "test-compiled-contract-1", + solidity_input_json: "{}", + contract_creation_bytecode: "0x", + abi: JSON.stringify("some-abi"), + contract_name: "TestContract", +}; + +export const VALID_SMART_CONTRACT_CUSTOM_MODEL: SmartContractModel = { + smart_contract_id: "test-smart-contract-custom", + network_id: Coinbase.networks.BaseSepolia, + wallet_id: walletId, + contract_name: "TestContract", + is_external: false, + contract_address: "0xcontract-address", + type: SmartContractType.Custom, + abi: JSON.stringify("some-abi"), + transaction: { + network_id: Coinbase.networks.BaseSepolia, + from_address_id: "0xdeadbeef", + unsigned_payload: "7b2274797065223a22307832222c22636861696e4964223a2230783134613334222c226e6f6e6365223a22307830222c22746f223a22307861383261623835303466646562326461646161336234663037356539363762626533353036356239222c22676173223a22307865623338222c226761735072696365223a6e756c6c2c226d61785072696f72697479466565506572476173223a2230786634323430222c226d6178466565506572476173223a2230786634333638222c2276616c7565223a22307830222c22696e707574223a223078366136323738343230303030303030303030303030303030303030303030303034373564343164653761383132393862613236333138343939363830306362636161643733633062222c226163636573734c697374223a5b5d2c2276223a22307830222c2272223a22307830222c2273223a22307830222c2279506172697479223a22307830222c2268617368223a22307865333131636632303063643237326639313566656433323165663065376431653965353362393761346166623737336638653935646431343630653665326163227d", + status: TransactionStatusEnum.Pending, + }, +}; + export const VALID_SMART_CONTRACT_EXTERNAL_MODEL: SmartContractModel = { smart_contract_id: "test-smart-contract-external", network_id: Coinbase.networks.BaseSepolia, @@ -786,6 +812,7 @@ export const contractEventApiMock = { }; export const smartContractApiMock = { + compileSmartContract: jest.fn(), createSmartContract: jest.fn(), deploySmartContract: jest.fn(), getSmartContract: jest.fn(), diff --git a/src/tests/wallet_address_test.ts b/src/tests/wallet_address_test.ts index 3a2a8246..474ad525 100644 --- a/src/tests/wallet_address_test.ts +++ b/src/tests/wallet_address_test.ts @@ -60,6 +60,8 @@ import { ERC721_BASE_URI, VALID_SMART_CONTRACT_ERC1155_MODEL, ERC1155_URI, + VALID_SMART_CONTRACT_CUSTOM_MODEL, + VALID_COMPILED_CONTRACT_MODEL, } from "./utils"; import { Transfer } from "../coinbase/transfer"; import { TransactionStatus } from "../coinbase/types"; @@ -2460,6 +2462,284 @@ describe("WalletAddress", () => { }); }); + describe("#deployContract", () => { + let key = ethers.Wallet.createRandom(); + let addressModel: AddressModel; + let walletAddress: WalletAddress; + let expectedSignedPayload: string; + + beforeAll(() => { + Coinbase.apiClients.smartContract = smartContractApiMock; + }); + + beforeEach(() => { + jest.clearAllMocks(); + + addressModel = newAddressModel(randomUUID(), randomUUID(), Coinbase.networks.BaseSepolia); + }); + + describe("when not using a server-signer", () => { + beforeEach(async () => { + Coinbase.useServerSigner = false; + + walletAddress = new WalletAddress(addressModel, key as unknown as ethers.Wallet); + + const tx = new Transaction(VALID_SMART_CONTRACT_CUSTOM_MODEL.transaction!); + expectedSignedPayload = await tx.sign(key as unknown as ethers.Wallet); + }); + + describe("when it is successful", () => { + let smartContract; + + beforeEach(async () => { + Coinbase.apiClients.smartContract!.compileSmartContract = mockReturnValue({ + ...VALID_COMPILED_CONTRACT_MODEL, + }); + + Coinbase.apiClients.smartContract!.createSmartContract = mockReturnValue({ + ...VALID_SMART_CONTRACT_CUSTOM_MODEL, + deployer_address: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + Coinbase.apiClients.smartContract!.deploySmartContract = mockReturnValue({ + ...VALID_SMART_CONTRACT_CUSTOM_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + smartContract = await walletAddress.deployContract({ + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: { + arg1: "arg1", + arg2: "arg2", + }, + }); + }); + + it("returns a smart contract", async () => { + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract.getId()).toBe(VALID_SMART_CONTRACT_CUSTOM_MODEL.smart_contract_id); + }); + + it("creates the smart contract", async () => { + expect(Coinbase.apiClients.smartContract!.createSmartContract).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + type: SmartContractType.Custom, + options: `{"arg1":"arg1","arg2":"arg2"}`, + compiled_smart_contract_id: "test-compiled-contract-1", + }, + ); + expect(Coinbase.apiClients.smartContract!.createSmartContract).toHaveBeenCalledTimes(1); + }); + + it("broadcasts the smart contract", async () => { + expect(Coinbase.apiClients.smartContract!.deploySmartContract).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + VALID_SMART_CONTRACT_CUSTOM_MODEL.smart_contract_id, + { + signed_payload: expectedSignedPayload, + }, + ); + + expect(Coinbase.apiClients.smartContract!.deploySmartContract).toHaveBeenCalledTimes(1); + }); + }); + + describe("when it is successful deploying a smart contract", () => { + let smartContract; + + beforeEach(async () => { + Coinbase.apiClients.smartContract!.createSmartContract = mockReturnValue({ + ...VALID_SMART_CONTRACT_CUSTOM_MODEL, + deployer_address: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + Coinbase.apiClients.smartContract!.deploySmartContract = mockReturnValue({ + ...VALID_SMART_CONTRACT_CUSTOM_MODEL, + deployer_address: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + Coinbase.apiClients.smartContract!.getSmartContract = mockReturnValue( + VALID_SMART_CONTRACT_CUSTOM_MODEL, + ); + + smartContract = await walletAddress.deployContract({ + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: { + arg1: "arg1", + arg2: "arg2", + }, + }); + }); + + it("returns a smart contract", async () => { + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract.getId()).toBe(VALID_SMART_CONTRACT_CUSTOM_MODEL.smart_contract_id); + }); + + it("creates the smart contract", async () => { + expect(Coinbase.apiClients.smartContract!.createSmartContract).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + type: SmartContractType.Custom, + options: `{"arg1":"arg1","arg2":"arg2"}`, + compiled_smart_contract_id: "test-compiled-contract-1", + }, + ); + expect(Coinbase.apiClients.smartContract!.createSmartContract).toHaveBeenCalledTimes(1); + }); + + it("broadcasts the smart contract", async () => { + expect(Coinbase.apiClients.smartContract!.deploySmartContract).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + VALID_SMART_CONTRACT_CUSTOM_MODEL.smart_contract_id, + { + signed_payload: expectedSignedPayload, + }, + ); + + expect(Coinbase.apiClients.smartContract!.deploySmartContract).toHaveBeenCalledTimes(1); + }); + }); + + describe("when no key is loaded", () => { + beforeEach(() => { + walletAddress = new WalletAddress(addressModel); + }); + + it("throws an error", async () => { + await expect( + walletAddress.deployContract({ + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: ["arg1", "arg2"], + }), + ).rejects.toThrow(Error); + }); + }); + + describe("when it fails to create a smart contract", () => { + beforeEach(() => { + Coinbase.apiClients.smartContract!.createSmartContract = mockReturnRejectedValue( + new APIError({ + response: { + status: 400, + data: { + code: "malformed_request", + message: "failed to create smart contract: invalid abi", + }, + }, + } as AxiosError), + ); + }); + + it("throws an error", async () => { + await expect( + walletAddress.deployContract({ + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: ["arg1", "arg2"], + }), + ).rejects.toThrow(APIError); + }); + }); + + describe("when it fails to broadcast a smart contract", () => { + beforeEach(() => { + Coinbase.apiClients.smartContract!.createSmartContract = mockReturnValue({ + ...VALID_SMART_CONTRACT_CUSTOM_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + Coinbase.apiClients.smartContract!.deploySmartContract = mockReturnRejectedValue( + new APIError({ + response: { + status: 400, + data: { + code: "invalid_signed_payload", + message: "failed to broadcast smart contract: invalid signed payload", + }, + }, + } as AxiosError), + ); + }); + + it("throws an error", async () => { + await expect( + walletAddress.deployContract({ + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: ["arg1", "arg2"], + }), + ).rejects.toThrow(APIError); + }); + }); + }); + describe("when using a server-signer", () => { + let smartContract; + + beforeEach(() => { + Coinbase.useServerSigner = true; + walletAddress = new WalletAddress(addressModel); + }); + + describe("when it is successful", () => { + beforeEach(async () => { + Coinbase.apiClients.smartContract!.createSmartContract = mockReturnValue({ + ...VALID_SMART_CONTRACT_CUSTOM_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + smartContract = await walletAddress.deployContract({ + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: { + arg1: "arg1", + arg2: "arg2", + }, + }); + }); + + it("returns a pending contract invocation", async () => { + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract.getId()).toBe(VALID_SMART_CONTRACT_CUSTOM_MODEL.smart_contract_id); + expect(smartContract.getTransaction().getStatus()).toBe(TransactionStatus.PENDING); + }); + + it("creates a contract invocation", async () => { + expect(Coinbase.apiClients.smartContract!.createSmartContract).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + type: SmartContractType.Custom, + options: `{"arg1":"arg1","arg2":"arg2"}`, + compiled_smart_contract_id: "test-compiled-contract-1", + }, + ); + expect(Coinbase.apiClients.smartContract!.createSmartContract).toHaveBeenCalledTimes(1); + }); + }); + }); + }); + describe("#createPayloadSignature", () => { let key = ethers.Wallet.createRandom(); let addressModel: AddressModel; diff --git a/src/tests/wallet_test.ts b/src/tests/wallet_test.ts index e09a8fad..48690c7f 100644 --- a/src/tests/wallet_test.ts +++ b/src/tests/wallet_test.ts @@ -61,6 +61,8 @@ import { ERC721_SYMBOL, ERC721_BASE_URI, VALID_SMART_CONTRACT_ERC721_MODEL, + VALID_SMART_CONTRACT_ERC1155_MODEL, + VALID_SMART_CONTRACT_CUSTOM_MODEL, } from "./utils"; import { Trade } from "../coinbase/trade"; import { WalletAddress } from "../coinbase/address/wallet_address"; @@ -680,6 +682,59 @@ describe("Wallet Class", () => { }); }); + describe("#deployMultiToken", () => { + let expectedSmartContract; + let options = { + uri: "https://example.com/metadata", + }; + + beforeEach(async () => { + expectedSmartContract = SmartContract.fromModel(VALID_SMART_CONTRACT_ERC1155_MODEL); + + (await wallet.getDefaultAddress()).deployMultiToken = jest + .fn() + .mockResolvedValue(expectedSmartContract); + }); + + it("successfully deploys an ERC1155 contract on the default address", async () => { + const smartContract = await wallet.deployMultiToken(options); + + expect((await wallet.getDefaultAddress()).deployMultiToken).toHaveBeenCalledTimes(1); + expect((await wallet.getDefaultAddress()).deployMultiToken).toHaveBeenCalledWith(options); + + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract).toEqual(expectedSmartContract); + }); + }); + + describe("#deployContract", () => { + let expectedSmartContract; + let options = { + solidityVersion: "0.8.0", + solidityInputJson: "{}", + contractName: "TestContract", + constructorArgs: ["arg1", "arg2"], + }; + + beforeEach(async () => { + expectedSmartContract = SmartContract.fromModel(VALID_SMART_CONTRACT_CUSTOM_MODEL); + + (await wallet.getDefaultAddress()).deployContract = jest + .fn() + .mockResolvedValue(expectedSmartContract); + }); + + it("successfully deploys a custom contract on the default address", async () => { + const smartContract = await wallet.deployContract(options); + + expect((await wallet.getDefaultAddress()).deployContract).toHaveBeenCalledTimes(1); + expect((await wallet.getDefaultAddress()).deployContract).toHaveBeenCalledWith(options); + + expect(smartContract).toBeInstanceOf(SmartContract); + expect(smartContract).toEqual(expectedSmartContract); + }); + }); + describe("#createPayloadSignature", () => { let unsignedPayload = VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL.unsigned_payload; let signature = From 9400b7520b1b4f546ca7d105b740ad9c385bf2bc Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 00:57:02 -0500 Subject: [PATCH 5/8] Formatting --- src/coinbase/address/wallet_address.ts | 116 ++++++++++++++----------- src/coinbase/types.ts | 2 +- src/tests/authenticator_test.ts | 10 ++- src/tests/smart_contract_test.ts | 4 +- src/tests/utils.ts | 4 +- src/tests/webhook_test.ts | 4 +- 6 files changed, 82 insertions(+), 58 deletions(-) diff --git a/src/coinbase/address/wallet_address.ts b/src/coinbase/address/wallet_address.ts index c3aaa06a..13fab8fb 100644 --- a/src/coinbase/address/wallet_address.ts +++ b/src/coinbase/address/wallet_address.ts @@ -381,7 +381,11 @@ export class WalletAddress extends Address { * @returns A Promise that resolves to the deployed SmartContract object. * @throws {APIError} If the API request to create a smart contract fails. */ - public async deployToken({ name, symbol, totalSupply }: CreateERC20Options): Promise { + public async deployToken({ + name, + symbol, + totalSupply, + }: CreateERC20Options): Promise { if (!Coinbase.useServerSigner && !this.key) { throw new Error("Cannot deploy ERC20 without private key loaded"); } @@ -449,8 +453,8 @@ export class WalletAddress extends Address { return smartContract; } - - /** + + /** * Deploys a custom contract. * * @param options - The options for creating the custom contract. @@ -461,23 +465,33 @@ export class WalletAddress extends Address { * @returns A Promise that resolves to the deployed SmartContract object. * @throws {APIError} If the API request to create a smart contract fails. */ - public async deployContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }: CreateCustomContractOptions): Promise { - if (!Coinbase.useServerSigner && !this.key) { - throw new Error("Cannot deploy custom contract without private key loaded"); - } - - const smartContract = await this.createCustomContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }); - - if (Coinbase.useServerSigner) { - return smartContract; - } - - await smartContract.sign(this.getSigner()); - await smartContract.broadcast(); - + public async deployContract({ + solidityVersion, + solidityInputJson, + contractName, + constructorArgs, + }: CreateCustomContractOptions): Promise { + if (!Coinbase.useServerSigner && !this.key) { + throw new Error("Cannot deploy custom contract without private key loaded"); + } + + const smartContract = await this.createCustomContract({ + solidityVersion, + solidityInputJson, + contractName, + constructorArgs, + }); + + if (Coinbase.useServerSigner) { return smartContract; } + await smartContract.sign(this.getSigner()); + await smartContract.broadcast(); + + return smartContract; + } + /** * Creates an ERC20 token contract. * @@ -489,7 +503,11 @@ export class WalletAddress extends Address { * @returns {Promise} A Promise that resolves to the created SmartContract. * @throws {APIError} If the API request to create a smart contract fails. */ - private async createERC20({ name, symbol, totalSupply }: CreateERC20Options): Promise { + private async createERC20({ + name, + symbol, + totalSupply, + }: CreateERC20Options): Promise { const resp = await Coinbase.apiClients.smartContract!.createSmartContract( this.getWalletId(), this.getId(), @@ -515,7 +533,11 @@ export class WalletAddress extends Address { * @returns A Promise that resolves to the deployed SmartContract object. * @throws {APIError} If the private key is not loaded when not using server signer. */ - private async createERC721({ name, symbol, baseURI }: CreateERC721Options): Promise { + private async createERC721({ + name, + symbol, + baseURI, + }: CreateERC721Options): Promise { const resp = await Coinbase.apiClients.smartContract!.createSmartContract( this.getWalletId(), this.getId(), @@ -566,30 +588,32 @@ export class WalletAddress extends Address { * @returns {Promise} A Promise that resolves to the created SmartContract. * @throws {APIError} If the API request to compile or subsequently create a smart contract fails. */ - private async createCustomContract({ solidityVersion, solidityInputJson, contractName, constructorArgs }: CreateCustomContractOptions): Promise { - const compileContractResp = await Coinbase.apiClients.smartContract!.compileSmartContract( - { - solidity_compiler_version: solidityVersion, - solidity_input_json: solidityInputJson, - contract_name: contractName, - }, - ); - - const compiledContract = compileContractResp.data; - const compiledContractId = compiledContract.compiled_smart_contract_id; + private async createCustomContract({ + solidityVersion, + solidityInputJson, + contractName, + constructorArgs, + }: CreateCustomContractOptions): Promise { + const compileContractResp = await Coinbase.apiClients.smartContract!.compileSmartContract({ + solidity_compiler_version: solidityVersion, + solidity_input_json: solidityInputJson, + contract_name: contractName, + }); - const createContractResp = await Coinbase.apiClients.smartContract!.createSmartContract( - this.getWalletId(), - this.getId(), - { - type: SmartContractType.Custom, - options: JSON.stringify(constructorArgs), - compiled_smart_contract_id: compiledContractId, - }, - ); - return SmartContract.fromModel(createContractResp?.data); - } + const compiledContract = compileContractResp.data; + const compiledContractId = compiledContract.compiled_smart_contract_id; + const createContractResp = await Coinbase.apiClients.smartContract!.createSmartContract( + this.getWalletId(), + this.getId(), + { + type: SmartContractType.Custom, + options: JSON.stringify(constructorArgs), + compiled_smart_contract_id: compiledContractId, + }, + ); + return SmartContract.fromModel(createContractResp?.data); + } /** * Creates a contract invocation with the given data. @@ -843,10 +867,7 @@ export class WalletAddress extends Address { * @param options.assetId - The ID of the Asset to fund with. For Ether, eth, gwei, and wei are supported. * @returns The created fund operation object */ - public async fund({ - amount, - assetId, - }: CreateFundOptions): Promise { + public async fund({ amount, assetId }: CreateFundOptions): Promise { const normalizedAmount = new Decimal(amount.toString()); return FundOperation.create( @@ -866,10 +887,7 @@ export class WalletAddress extends Address { * @param options.assetId - The ID of the Asset to fund with. For Ether, eth, gwei, and wei are supported. * @returns The fund quote object */ - public async quoteFund({ - amount, - assetId, - }: CreateQuoteOptions): Promise { + public async quoteFund({ amount, assetId }: CreateQuoteOptions): Promise { const normalizedAmount = new Decimal(amount.toString()); return FundQuote.create( diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 810560e9..f2d77ead 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -1144,7 +1144,7 @@ export type CreateCustomContractOptions = { solidityVersion: string; solidityInputJson: string; contractName: string; - constructorArgs: Record + constructorArgs: Record; }; /** diff --git a/src/tests/authenticator_test.ts b/src/tests/authenticator_test.ts index e6482a5d..9e5cf5a1 100644 --- a/src/tests/authenticator_test.ts +++ b/src/tests/authenticator_test.ts @@ -54,8 +54,8 @@ describe("Authenticator tests", () => { }); describe("when a source version is provided", () => { - beforeAll(() => sourceVersion = "1.0.0"); - afterAll(() => sourceVersion = undefined); + beforeAll(() => (sourceVersion = "1.0.0")); + afterAll(() => (sourceVersion = undefined)); it("includes the source version in the correlation context", async () => { const config = await authenticator.authenticateRequest(VALID_CONFIG, true); @@ -65,7 +65,11 @@ describe("Authenticator tests", () => { }); it("invalid pem key should raise an InvalidAPIKeyFormat error", async () => { - const invalidAuthenticator = new CoinbaseAuthenticator("test-key", "-----BEGIN EC KEY-----\n", source); + const invalidAuthenticator = new CoinbaseAuthenticator( + "test-key", + "-----BEGIN EC KEY-----\n", + source, + ); expect(invalidAuthenticator.authenticateRequest(VALID_CONFIG)).rejects.toThrow(); }); diff --git a/src/tests/smart_contract_test.ts b/src/tests/smart_contract_test.ts index e396a5bb..459a2c02 100644 --- a/src/tests/smart_contract_test.ts +++ b/src/tests/smart_contract_test.ts @@ -564,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()}'}`, ); }); }); diff --git a/src/tests/utils.ts b/src/tests/utils.ts index 337d84ad..fe7fb62c 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -411,7 +411,8 @@ export const VALID_SMART_CONTRACT_CUSTOM_MODEL: SmartContractModel = { transaction: { network_id: Coinbase.networks.BaseSepolia, from_address_id: "0xdeadbeef", - unsigned_payload: "7b2274797065223a22307832222c22636861696e4964223a2230783134613334222c226e6f6e6365223a22307830222c22746f223a22307861383261623835303466646562326461646161336234663037356539363762626533353036356239222c22676173223a22307865623338222c226761735072696365223a6e756c6c2c226d61785072696f72697479466565506572476173223a2230786634323430222c226d6178466565506572476173223a2230786634333638222c2276616c7565223a22307830222c22696e707574223a223078366136323738343230303030303030303030303030303030303030303030303034373564343164653761383132393862613236333138343939363830306362636161643733633062222c226163636573734c697374223a5b5d2c2276223a22307830222c2272223a22307830222c2273223a22307830222c2279506172697479223a22307830222c2268617368223a22307865333131636632303063643237326639313566656433323165663065376431653965353362393761346166623737336638653935646431343630653665326163227d", + unsigned_payload: + "7b2274797065223a22307832222c22636861696e4964223a2230783134613334222c226e6f6e6365223a22307830222c22746f223a22307861383261623835303466646562326461646161336234663037356539363762626533353036356239222c22676173223a22307865623338222c226761735072696365223a6e756c6c2c226d61785072696f72697479466565506572476173223a2230786634323430222c226d6178466565506572476173223a2230786634333638222c2276616c7565223a22307830222c22696e707574223a223078366136323738343230303030303030303030303030303030303030303030303034373564343164653761383132393862613236333138343939363830306362636161643733633062222c226163636573734c697374223a5b5d2c2276223a22307830222c2272223a22307830222c2273223a22307830222c2279506172697479223a22307830222c2268617368223a22307865333131636632303063643237326639313566656433323165663065376431653965353362393761346166623737336638653935646431343630653665326163227d", status: TransactionStatusEnum.Pending, }, }; @@ -426,7 +427,6 @@ export const VALID_SMART_CONTRACT_EXTERNAL_MODEL: SmartContractModel = { abi: JSON.stringify("some-abi"), }; - const asset = Asset.fromModel({ asset_id: Coinbase.assets.Eth, network_id: "base-sepolia", diff --git a/src/tests/webhook_test.ts b/src/tests/webhook_test.ts index 448124ce..822105fe 100644 --- a/src/tests/webhook_test.ts +++ b/src/tests/webhook_test.ts @@ -228,7 +228,9 @@ describe("Webhook", () => { it("should update the webhook address list only", async () => { const webhook = Webhook.init(mockModel); - await webhook.update({ eventTypeFilter: { wallet_id: "test-wallet-id", addresses: ["0x1..", "0x2.."] } }); + await webhook.update({ + eventTypeFilter: { wallet_id: "test-wallet-id", addresses: ["0x1..", "0x2.."] }, + }); expect(Coinbase.apiClients.webhook!.updateWebhook).toHaveBeenCalledWith("test-id", { notification_uri: "https://example.com/callback", From be5056ccac2ee212f1387566d4b9e6bf6f36ddac Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 01:01:23 -0500 Subject: [PATCH 6/8] Changes --- src/coinbase/smart_contract.ts | 7 +++++++ src/coinbase/types.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/coinbase/smart_contract.ts b/src/coinbase/smart_contract.ts index af5bccf0..f7022aa5 100644 --- a/src/coinbase/smart_contract.ts +++ b/src/coinbase/smart_contract.ts @@ -455,6 +455,13 @@ export class SmartContract { return type === SmartContractType.ERC721; } + /** + * Type guard for checking if the smart contract is an ERC1155. + * + * @param type - The type of the smart contract. + * @param options - The options of the smart contract. + * @returns True if the smart contract is an ERC1155, false otherwise. + */ private isERC1155( type: SmartContractType, options: SmartContractOptionsModel, diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index f2d77ead..d1688c1d 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -1144,6 +1144,7 @@ export type CreateCustomContractOptions = { solidityVersion: string; solidityInputJson: string; contractName: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any constructorArgs: Record; }; @@ -1483,6 +1484,7 @@ export interface SmartContractAPIClient { /** * Compiles a custom contract. + * * @param compileSmartContractRequest - The request body containing the compile smart contract details. * @param options - Axios request options. * @returns - A promise resolving to the compiled smart contract. From 0e3cd04a549b8bc14ab29bf27e3b9cb59de43b57 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 01:06:33 -0500 Subject: [PATCH 7/8] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8f22cb5..81bb12b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Coinbase Node.js SDK Changelog +### Unreleased + +- Add `deployContract` method to `WalletAddress` and `Wallet` to deploy an arbitrary contract. + ## [0.14.0] - 2025-01-14 ### Added From dd1c48617613b9ef01e30a1feea496d97222829d Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 17 Jan 2025 17:05:33 -0500 Subject: [PATCH 8/8] feedback --- src/coinbase/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index d1688c1d..f62a2e1c 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -1141,9 +1141,13 @@ export type CreateERC1155Options = { * Options for creating an arbitrary contract. */ export type CreateCustomContractOptions = { + /** The version of the solidity compiler, must be 0.8.+, such as "0.8.28+commit.7893614a". See https://binaries.soliditylang.org/bin/list.json */ solidityVersion: string; + /** The input json for the solidity compiler. See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description for more details. */ solidityInputJson: string; + /** The name of the contract class to be deployed. */ contractName: string; + /** The arguments for the constructor. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any constructorArgs: Record; };