diff --git a/CHANGELOG.md b/CHANGELOG.md index e3807a38..ad0b4c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,21 @@ ## Unreleased +## [0.3.0] - 2024-09-05 + +### Added + +- Add support for list address transactions. +- Add support for exporting the private key of a `WalletAddress` +- Add support for creating arbitrary payload signatures. +- Add support for invoking Smart Contracts using MPC and Developer-managed Wallets. + ## [0.2.0] ### Added - USDC Faucet support on Base Sepolia -- Improved error mesasges for `InternalError` +- Improved error messages for `InternalError` ## [0.1.1] - 2024-08-27 diff --git a/jest.config.js b/jest.config.js index 38b02612..9873b452 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,7 +12,7 @@ module.exports = { "./src/coinbase/**": { branches: 80, functions: 90, - statements: 90, + statements: 85, lines: 90, }, }, diff --git a/package-lock.json b/package-lock.json index 140345dd..48ac6586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@coinbase/coinbase-sdk", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@coinbase/coinbase-sdk", - "version": "0.2.0", + "version": "0.3.0", "license": "ISC", "dependencies": { "@scure/bip32": "^1.4.0", diff --git a/package.json b/package.json index be845a53..337c4889 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "ISC", "description": "Coinbase Platform SDK", "repository": "https://github.com/coinbase/coinbase-sdk-nodejs", - "version": "0.2.0", + "version": "0.3.0", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { diff --git a/quickstart-template/package.json b/quickstart-template/package.json index 8a0edc45..a0d44563 100644 --- a/quickstart-template/package.json +++ b/quickstart-template/package.json @@ -14,7 +14,7 @@ "license": "ISC", "type": "module", "dependencies": { - "@coinbase/coinbase-sdk": "^0.1.0", + "@coinbase/coinbase-sdk": "^0.3.0", "csv-parse": "^5.5.6", "csv-writer": "^1.6.0" } diff --git a/src/client/api.ts b/src/client/api.ts index 3b10540d..66ad7e21 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -147,6 +147,31 @@ export interface AddressList { */ 'total_count': number; } +/** + * + * @export + * @interface AddressTransactionList + */ +export interface AddressTransactionList { + /** + * + * @type {Array} + * @memberof AddressTransactionList + */ + 'data': Array; + /** + * True if this list has another page of items after this one that can be fetched. + * @type {boolean} + * @memberof AddressTransactionList + */ + 'has_more': boolean; + /** + * The page token to be used to fetch the next page. + * @type {string} + * @memberof AddressTransactionList + */ + 'next_page': string; +} /** * An asset onchain scoped to a particular network, e.g. ETH on base-sepolia, or the USDC ERC20 Token on ethereum-mainnet. * @export @@ -197,6 +222,19 @@ export interface Balance { */ 'asset': Asset; } +/** + * + * @export + * @interface BroadcastContractInvocationRequest + */ +export interface BroadcastContractInvocationRequest { + /** + * The hex-encoded signed payload of the contract invocation + * @type {string} + * @memberof BroadcastContractInvocationRequest + */ + 'signed_payload': string; +} /** * * @export @@ -395,6 +433,98 @@ export interface ContractEventList { */ 'has_more': boolean; } +/** + * A contract invocation onchain. + * @export + * @interface ContractInvocation + */ +export interface ContractInvocation { + /** + * The ID of the blockchain network. + * @type {string} + * @memberof ContractInvocation + */ + 'network_id': string; + /** + * The ID of the wallet that owns the address. + * @type {string} + * @memberof ContractInvocation + */ + 'wallet_id': string; + /** + * The onchain address of the address invoking the contract. + * @type {string} + * @memberof ContractInvocation + */ + 'address_id': string; + /** + * The ID of the contract invocation. + * @type {string} + * @memberof ContractInvocation + */ + 'contract_invocation_id': string; + /** + * The onchain address of the contract. + * @type {string} + * @memberof ContractInvocation + */ + 'contract_address': string; + /** + * The method to be invoked on the contract. + * @type {string} + * @memberof ContractInvocation + */ + 'method': string; + /** + * The JSON-encoded arguments to pass to the contract method. The keys should be the argument names and the values should be the argument values. + * @type {string} + * @memberof ContractInvocation + */ + 'args': string; + /** + * The JSON-encoded ABI of the contract. + * @type {string} + * @memberof ContractInvocation + */ + 'abi'?: string; + /** + * + * @type {Transaction} + * @memberof ContractInvocation + */ + 'transaction': Transaction; +} +/** + * + * @export + * @interface ContractInvocationList + */ +export interface ContractInvocationList { + /** + * + * @type {Array} + * @memberof ContractInvocationList + */ + 'data': Array; + /** + * True if this list has another page of items after this one that can be fetched. + * @type {boolean} + * @memberof ContractInvocationList + */ + 'has_more': boolean; + /** + * The page token to be used to fetch the next page. + * @type {string} + * @memberof ContractInvocationList + */ + 'next_page': string; + /** + * The total number of contract invocations for the address in the wallet. + * @type {number} + * @memberof ContractInvocationList + */ + 'total_count': number; +} /** * * @export @@ -420,6 +550,56 @@ export interface CreateAddressRequest { */ 'address_index'?: number; } +/** + * + * @export + * @interface CreateContractInvocationRequest + */ +export interface CreateContractInvocationRequest { + /** + * The address of the contract to invoke. + * @type {string} + * @memberof CreateContractInvocationRequest + */ + 'contract_address': string; + /** + * The method to invoke on the contract. + * @type {string} + * @memberof CreateContractInvocationRequest + */ + 'method': string; + /** + * The JSON-encoded arguments to pass to the contract method. The keys should be the argument names and the values should be the argument values. + * @type {string} + * @memberof CreateContractInvocationRequest + */ + 'args': string; + /** + * The JSON-encoded ABI of the contract. + * @type {string} + * @memberof CreateContractInvocationRequest + */ + 'abi'?: string; +} +/** + * + * @export + * @interface CreatePayloadSignatureRequest + */ +export interface CreatePayloadSignatureRequest { + /** + * The unsigned payload. + * @type {string} + * @memberof CreatePayloadSignatureRequest + */ + 'unsigned_payload': string; + /** + * The signature of the payload. + * @type {string} + * @memberof CreatePayloadSignatureRequest + */ + 'signature'?: string; +} /** * * @export @@ -601,7 +781,7 @@ export interface CreateWebhookRequest { */ 'notification_uri': string; /** - * The custom header to be used for x-webhook-signature header on callbacks, so developers can verify the requests are coming from Coinbase. + * The custom header to be used for x-webhook-signature header on callbacks, so developers can verify the requests are coming from Coinbase. * @type {string} * @memberof CreateWebhookRequest */ @@ -610,173 +790,435 @@ export interface CreateWebhookRequest { /** - * An Ethereum validator. + * * @export - * @interface EthereumValidatorMetadata + * @interface EthereumTransaction */ -export interface EthereumValidatorMetadata { +export interface EthereumTransaction { /** - * The index of the validator in the validator set. + * The onchain address of the sender. * @type {string} - * @memberof EthereumValidatorMetadata + * @memberof EthereumTransaction */ - 'index': string; + 'from': string; /** - * The public key of the validator. - * @type {string} - * @memberof EthereumValidatorMetadata + * The amount of gas spent in the transaction. + * @type {number} + * @memberof EthereumTransaction */ - 'public_key': string; + 'gas'?: number; /** - * The address to which the validator\'s rewards are sent. - * @type {string} - * @memberof EthereumValidatorMetadata + * The price per gas spent in the transaction in atomic units of the native asset. + * @type {number} + * @memberof EthereumTransaction */ - 'withdrawal_address': string; + 'gas_price'?: number; /** - * Whether the validator has been slashed. - * @type {boolean} - * @memberof EthereumValidatorMetadata + * The hash of the transaction as a hexadecimal string, prefixed with 0x. + * @type {string} + * @memberof EthereumTransaction */ - 'slashed': boolean; + 'hash'?: string; /** - * The epoch at which the validator was activated. + * The input data of the transaction. * @type {string} - * @memberof EthereumValidatorMetadata + * @memberof EthereumTransaction */ - 'activationEpoch': string; + 'input'?: string; /** - * The epoch at which the validator exited. - * @type {string} - * @memberof EthereumValidatorMetadata + * The nonce of the transaction in the source address. + * @type {number} + * @memberof EthereumTransaction */ - 'exitEpoch': string; + 'nonce'?: number; /** - * The epoch at which the validator can withdraw. + * The onchain address of the receiver. * @type {string} - * @memberof EthereumValidatorMetadata + * @memberof EthereumTransaction */ - 'withdrawableEpoch': string; + 'to': string; /** - * - * @type {Balance} - * @memberof EthereumValidatorMetadata + * The index of the transaction in the block. + * @type {number} + * @memberof EthereumTransaction */ - 'balance': Balance; + 'index'?: number; /** - * - * @type {Balance} - * @memberof EthereumValidatorMetadata + * The value of the transaction in atomic units of the native asset. + * @type {string} + * @memberof EthereumTransaction */ - 'effective_balance': Balance; -} -/** - * The faucet transaction - * @export - * @interface FaucetTransaction - */ -export interface FaucetTransaction { + 'value'?: string; /** - * The transaction hash of the transaction the faucet created. - * @type {string} - * @memberof FaucetTransaction + * The EIP-2718 transaction type. See https://eips.ethereum.org/EIPS/eip-2718 for more details. + * @type {number} + * @memberof EthereumTransaction */ - 'transaction_hash': string; + 'type'?: number; /** - * Link to the transaction on the blockchain explorer. - * @type {string} - * @memberof FaucetTransaction + * The max fee per gas as defined in EIP-1559. https://eips.ethereum.org/EIPS/eip-1559 for more details. + * @type {number} + * @memberof EthereumTransaction */ - 'transaction_link': string; -} -/** - * - * @export - * @interface FeatureSet - */ -export interface FeatureSet { + 'max_fee_per_gas'?: number; /** - * Whether the network supports a faucet - * @type {boolean} - * @memberof FeatureSet + * The max priority fee per gas as defined in EIP-1559. https://eips.ethereum.org/EIPS/eip-1559 for more details. + * @type {number} + * @memberof EthereumTransaction */ - 'faucet': boolean; + 'max_priority_fee_per_gas'?: number; /** - * Whether the network supports Server-Signers - * @type {boolean} - * @memberof FeatureSet + * The confirmed priority fee per gas as defined in EIP-1559. https://eips.ethereum.org/EIPS/eip-1559 for more details. + * @type {number} + * @memberof EthereumTransaction */ - 'server_signer': boolean; + 'priority_fee_per_gas'?: number; /** - * Whether the network supports transfers - * @type {boolean} - * @memberof FeatureSet + * + * @type {EthereumTransactionAccessList} + * @memberof EthereumTransaction */ - 'transfer': boolean; + 'transaction_access_list'?: EthereumTransactionAccessList; /** - * Whether the network supports trading - * @type {boolean} - * @memberof FeatureSet + * + * @type {Array} + * @memberof EthereumTransaction */ - 'trade': boolean; + 'flattened_traces'?: Array; /** - * Whether the network supports staking - * @type {boolean} - * @memberof FeatureSet + * The timestamp of the block in which the event was emitted + * @type {string} + * @memberof EthereumTransaction */ - 'stake': boolean; + 'block_timestamp'?: string; /** - * Whether the network supports gasless sends - * @type {boolean} - * @memberof FeatureSet + * This is for handling optimism rollup specific EIP-2718 transaction type field. + * @type {string} + * @memberof EthereumTransaction */ - 'gasless_send': boolean; + 'mint'?: string; } /** * * @export - * @interface FetchHistoricalStakingBalances200Response + * @interface EthereumTransactionAccess */ -export interface FetchHistoricalStakingBalances200Response { +export interface EthereumTransactionAccess { /** * - * @type {Array} - * @memberof FetchHistoricalStakingBalances200Response + * @type {string} + * @memberof EthereumTransactionAccess */ - 'data': Array; + 'address'?: string; /** - * True if this list has another page of items after this one that can be fetched. - * @type {boolean} - * @memberof FetchHistoricalStakingBalances200Response + * + * @type {Array} + * @memberof EthereumTransactionAccess */ - 'has_more': boolean; + 'storage_keys'?: Array; +} +/** + * + * @export + * @interface EthereumTransactionAccessList + */ +export interface EthereumTransactionAccessList { /** - * The page token to be used to fetch the next page. - * @type {string} - * @memberof FetchHistoricalStakingBalances200Response + * + * @type {Array} + * @memberof EthereumTransactionAccessList */ - 'next_page': string; + 'access_list'?: Array; } /** * * @export - * @interface FetchStakingRewards200Response + * @interface EthereumTransactionFlattenedTrace */ -export interface FetchStakingRewards200Response { +export interface EthereumTransactionFlattenedTrace { /** * - * @type {Array} - * @memberof FetchStakingRewards200Response + * @type {string} + * @memberof EthereumTransactionFlattenedTrace */ - 'data': Array; + 'error'?: string; /** - * True if this list has another page of items after this one that can be fetched. - * @type {boolean} - * @memberof FetchStakingRewards200Response + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace */ - 'has_more': boolean; + 'type'?: string; /** - * The page token to be used to fetch the next page. + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'from'?: string; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'to'?: string; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'value'?: string; + /** + * + * @type {number} + * @memberof EthereumTransactionFlattenedTrace + */ + 'gas'?: number; + /** + * + * @type {number} + * @memberof EthereumTransactionFlattenedTrace + */ + 'gas_used'?: number; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'input'?: string; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'output'?: string; + /** + * + * @type {number} + * @memberof EthereumTransactionFlattenedTrace + */ + 'sub_traces'?: number; + /** + * + * @type {Array} + * @memberof EthereumTransactionFlattenedTrace + */ + 'trace_address'?: Array; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'trace_type'?: string; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'call_type'?: string; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'trace_id'?: string; + /** + * + * @type {number} + * @memberof EthereumTransactionFlattenedTrace + */ + 'status'?: number; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'block_hash'?: string; + /** + * + * @type {number} + * @memberof EthereumTransactionFlattenedTrace + */ + 'block_number'?: number; + /** + * + * @type {string} + * @memberof EthereumTransactionFlattenedTrace + */ + 'transaction_hash'?: string; + /** + * + * @type {number} + * @memberof EthereumTransactionFlattenedTrace + */ + 'transaction_index'?: number; +} +/** + * An Ethereum validator. + * @export + * @interface EthereumValidatorMetadata + */ +export interface EthereumValidatorMetadata { + /** + * The index of the validator in the validator set. + * @type {string} + * @memberof EthereumValidatorMetadata + */ + 'index': string; + /** + * The public key of the validator. + * @type {string} + * @memberof EthereumValidatorMetadata + */ + 'public_key': string; + /** + * The address to which the validator\'s rewards are sent. + * @type {string} + * @memberof EthereumValidatorMetadata + */ + 'withdrawal_address': string; + /** + * Whether the validator has been slashed. + * @type {boolean} + * @memberof EthereumValidatorMetadata + */ + 'slashed': boolean; + /** + * The epoch at which the validator was activated. + * @type {string} + * @memberof EthereumValidatorMetadata + */ + 'activationEpoch': string; + /** + * The epoch at which the validator exited. + * @type {string} + * @memberof EthereumValidatorMetadata + */ + 'exitEpoch': string; + /** + * The epoch at which the validator can withdraw. + * @type {string} + * @memberof EthereumValidatorMetadata + */ + 'withdrawableEpoch': string; + /** + * + * @type {Balance} + * @memberof EthereumValidatorMetadata + */ + 'balance': Balance; + /** + * + * @type {Balance} + * @memberof EthereumValidatorMetadata + */ + 'effective_balance': Balance; +} +/** + * The faucet transaction + * @export + * @interface FaucetTransaction + */ +export interface FaucetTransaction { + /** + * The transaction hash of the transaction the faucet created. + * @type {string} + * @memberof FaucetTransaction + */ + 'transaction_hash': string; + /** + * Link to the transaction on the blockchain explorer. + * @type {string} + * @memberof FaucetTransaction + */ + 'transaction_link': string; +} +/** + * + * @export + * @interface FeatureSet + */ +export interface FeatureSet { + /** + * Whether the network supports a faucet + * @type {boolean} + * @memberof FeatureSet + */ + 'faucet': boolean; + /** + * Whether the network supports Server-Signers + * @type {boolean} + * @memberof FeatureSet + */ + 'server_signer': boolean; + /** + * Whether the network supports transfers + * @type {boolean} + * @memberof FeatureSet + */ + 'transfer': boolean; + /** + * Whether the network supports trading + * @type {boolean} + * @memberof FeatureSet + */ + 'trade': boolean; + /** + * Whether the network supports staking + * @type {boolean} + * @memberof FeatureSet + */ + 'stake': boolean; + /** + * Whether the network supports gasless sends + * @type {boolean} + * @memberof FeatureSet + */ + 'gasless_send': boolean; +} +/** + * + * @export + * @interface FetchHistoricalStakingBalances200Response + */ +export interface FetchHistoricalStakingBalances200Response { + /** + * + * @type {Array} + * @memberof FetchHistoricalStakingBalances200Response + */ + 'data': Array; + /** + * True if this list has another page of items after this one that can be fetched. + * @type {boolean} + * @memberof FetchHistoricalStakingBalances200Response + */ + 'has_more': boolean; + /** + * The page token to be used to fetch the next page. + * @type {string} + * @memberof FetchHistoricalStakingBalances200Response + */ + 'next_page': string; +} +/** + * + * @export + * @interface FetchStakingRewards200Response + */ +export interface FetchStakingRewards200Response { + /** + * + * @type {Array} + * @memberof FetchStakingRewards200Response + */ + 'data': Array; + /** + * True if this list has another page of items after this one that can be fetched. + * @type {boolean} + * @memberof FetchStakingRewards200Response + */ + 'has_more': boolean; + /** + * The page token to be used to fetch the next page. * @type {string} * @memberof FetchStakingRewards200Response */ @@ -988,36 +1430,119 @@ export type NetworkIdentifier = typeof NetworkIdentifier[keyof typeof NetworkIde /** - * An event representing a seed creation. + * A payload signed by an address. * @export - * @interface SeedCreationEvent + * @interface PayloadSignature */ -export interface SeedCreationEvent { +export interface PayloadSignature { /** - * The ID of the wallet that the server-signer should create the seed for + * The ID of the payload signature. * @type {string} - * @memberof SeedCreationEvent + * @memberof PayloadSignature */ - 'wallet_id': string; + 'payload_signature_id': string; /** - * The ID of the user that the wallet belongs to + * The ID of the wallet that owns the address. * @type {string} - * @memberof SeedCreationEvent + * @memberof PayloadSignature */ - 'wallet_user_id': string; -} -/** - * The result to a SeedCreationEvent. - * @export - * @interface SeedCreationEventResult - */ -export interface SeedCreationEventResult { + 'wallet_id': string; /** - * The ID of the wallet that the seed was created for + * The onchain address of the signer. * @type {string} - * @memberof SeedCreationEventResult + * @memberof PayloadSignature */ - 'wallet_id': string; + 'address_id': string; + /** + * The unsigned payload. This is the payload that needs to be signed by the signer address. + * @type {string} + * @memberof PayloadSignature + */ + 'unsigned_payload': string; + /** + * The signature of the payload. + * @type {string} + * @memberof PayloadSignature + */ + 'signature'?: string; + /** + * The status of the payload signature. + * @type {string} + * @memberof PayloadSignature + */ + 'status': PayloadSignatureStatusEnum; +} + +export const PayloadSignatureStatusEnum = { + Pending: 'pending', + Signed: 'signed', + Failed: 'failed' +} as const; + +export type PayloadSignatureStatusEnum = typeof PayloadSignatureStatusEnum[keyof typeof PayloadSignatureStatusEnum]; + +/** + * + * @export + * @interface PayloadSignatureList + */ +export interface PayloadSignatureList { + /** + * + * @type {Array} + * @memberof PayloadSignatureList + */ + 'data': Array; + /** + * True if this list has another page of items after this one that can be fetched. + * @type {boolean} + * @memberof PayloadSignatureList + */ + 'has_more': boolean; + /** + * The page token to be used to fetch the next page. + * @type {string} + * @memberof PayloadSignatureList + */ + 'next_page': string; + /** + * The total number of payload signatures for the address. + * @type {number} + * @memberof PayloadSignatureList + */ + 'total_count': number; +} +/** + * An event representing a seed creation. + * @export + * @interface SeedCreationEvent + */ +export interface SeedCreationEvent { + /** + * The ID of the wallet that the server-signer should create the seed for + * @type {string} + * @memberof SeedCreationEvent + */ + 'wallet_id': string; + /** + * The ID of the user that the wallet belongs to + * @type {string} + * @memberof SeedCreationEvent + */ + 'wallet_user_id': string; +} +/** + * The result to a SeedCreationEvent. + * @export + * @interface SeedCreationEventResult + */ +export interface SeedCreationEventResult { + /** + * The ID of the wallet that the seed was created for + * @type {string} + * @memberof SeedCreationEventResult + */ + 'wallet_id': string; /** * The ID of the user that the wallet belongs to * @type {string} @@ -1676,6 +2201,18 @@ export interface Transaction { * @memberof Transaction */ 'network_id': string; + /** + * The hash of the block at which the transaction was recorded. + * @type {string} + * @memberof Transaction + */ + 'block_hash'?: string; + /** + * The block height at which the transaction was recorded. + * @type {string} + * @memberof Transaction + */ + 'block_height'?: string; /** * The onchain address of the sender. * @type {string} @@ -1718,6 +2255,12 @@ export interface Transaction { * @memberof Transaction */ 'status': TransactionStatusEnum; + /** + * + * @type {TransactionContent} + * @memberof Transaction + */ + 'content'?: TransactionContent; } export const TransactionStatusEnum = { @@ -1725,11 +2268,18 @@ export const TransactionStatusEnum = { Signed: 'signed', Broadcast: 'broadcast', Complete: 'complete', - Failed: 'failed' + Failed: 'failed', + Unspecified: 'unspecified' } as const; export type TransactionStatusEnum = typeof TransactionStatusEnum[keyof typeof TransactionStatusEnum]; +/** + * @type TransactionContent + * @export + */ +export type TransactionContent = EthereumTransaction; + /** * * @export @@ -2257,6 +2807,48 @@ export const AddressesApiAxiosParamCreator = function (configuration?: Configura options: localVarRequestOptions, }; }, + /** + * Create a new payload signature with an address. + * @summary Create a new payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address to sign the payload with. + * @param {CreatePayloadSignatureRequest} [createPayloadSignatureRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createPayloadSignature: async (walletId: string, addressId: string, createPayloadSignatureRequest?: CreatePayloadSignatureRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('createPayloadSignature', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('createPayloadSignature', 'addressId', addressId) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/payload_signatures` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))); + // 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; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createPayloadSignatureRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Get address * @summary Get address by onchain address @@ -2328,6 +2920,48 @@ export const AddressesApiAxiosParamCreator = function (configuration?: Configura + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get payload signature. + * @summary Get payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address that signed the payload. + * @param {string} payloadSignatureId The ID of the payload signature to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPayloadSignature: async (walletId: string, addressId: string, payloadSignatureId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('getPayloadSignature', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('getPayloadSignature', 'addressId', addressId) + // verify required parameter 'payloadSignatureId' is not null or undefined + assertParamExists('getPayloadSignature', 'payloadSignatureId', payloadSignatureId) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/payload_signatures/{payload_signature_id}` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))) + .replace(`{${"payload_signature_id"}}`, encodeURIComponent(String(payloadSignatureId))); + // 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: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -2415,6 +3049,54 @@ export const AddressesApiAxiosParamCreator = function (configuration?: Configura + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List payload signatures for an address. + * @summary List payload signatures for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address whose payload signatures to fetch. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listPayloadSignatures: async (walletId: string, addressId: string, limit?: number, page?: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('listPayloadSignatures', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('listPayloadSignatures', 'addressId', addressId) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/payload_signatures` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))); + // 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: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (limit !== undefined) { + localVarQueryParameter['limit'] = limit; + } + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -2491,6 +3173,21 @@ export const AddressesApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['AddressesApi.createAddress']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Create a new payload signature with an address. + * @summary Create a new payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address to sign the payload with. + * @param {CreatePayloadSignatureRequest} [createPayloadSignatureRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createPayloadSignature(walletId: string, addressId: string, createPayloadSignatureRequest?: CreatePayloadSignatureRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createPayloadSignature(walletId, addressId, createPayloadSignatureRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['AddressesApi.createPayloadSignature']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Get address * @summary Get address by onchain address @@ -2520,6 +3217,21 @@ export const AddressesApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['AddressesApi.getAddressBalance']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Get payload signature. + * @summary Get payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address that signed the payload. + * @param {string} payloadSignatureId The ID of the payload signature to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getPayloadSignature(walletId: string, addressId: string, payloadSignatureId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPayloadSignature(walletId, addressId, payloadSignatureId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['AddressesApi.getPayloadSignature']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Get address balances * @summary Get all balances for address @@ -2550,6 +3262,22 @@ export const AddressesApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['AddressesApi.listAddresses']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * List payload signatures for an address. + * @summary List payload signatures for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address whose payload signatures to fetch. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listPayloadSignatures(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listPayloadSignatures(walletId, addressId, limit, page, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['AddressesApi.listPayloadSignatures']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Request faucet funds to be sent to onchain address. * @summary Request faucet funds for onchain address. @@ -2586,6 +3314,18 @@ export const AddressesApiFactory = function (configuration?: Configuration, base createAddress(walletId: string, createAddressRequest?: CreateAddressRequest, options?: any): AxiosPromise
{ return localVarFp.createAddress(walletId, createAddressRequest, options).then((request) => request(axios, basePath)); }, + /** + * Create a new payload signature with an address. + * @summary Create a new payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address to sign the payload with. + * @param {CreatePayloadSignatureRequest} [createPayloadSignatureRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createPayloadSignature(walletId: string, addressId: string, createPayloadSignatureRequest?: CreatePayloadSignatureRequest, options?: any): AxiosPromise { + return localVarFp.createPayloadSignature(walletId, addressId, createPayloadSignatureRequest, options).then((request) => request(axios, basePath)); + }, /** * Get address * @summary Get address by onchain address @@ -2609,6 +3349,18 @@ export const AddressesApiFactory = function (configuration?: Configuration, base getAddressBalance(walletId: string, addressId: string, assetId: string, options?: any): AxiosPromise { return localVarFp.getAddressBalance(walletId, addressId, assetId, options).then((request) => request(axios, basePath)); }, + /** + * Get payload signature. + * @summary Get payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address that signed the payload. + * @param {string} payloadSignatureId The ID of the payload signature to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPayloadSignature(walletId: string, addressId: string, payloadSignatureId: string, options?: any): AxiosPromise { + return localVarFp.getPayloadSignature(walletId, addressId, payloadSignatureId, options).then((request) => request(axios, basePath)); + }, /** * Get address balances * @summary Get all balances for address @@ -2633,6 +3385,19 @@ export const AddressesApiFactory = function (configuration?: Configuration, base listAddresses(walletId: string, limit?: number, page?: string, options?: any): AxiosPromise { return localVarFp.listAddresses(walletId, limit, page, options).then((request) => request(axios, basePath)); }, + /** + * List payload signatures for an address. + * @summary List payload signatures for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address whose payload signatures to fetch. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listPayloadSignatures(walletId: string, addressId: string, limit?: number, page?: string, options?: any): AxiosPromise { + return localVarFp.listPayloadSignatures(walletId, addressId, limit, page, options).then((request) => request(axios, basePath)); + }, /** * Request faucet funds to be sent to onchain address. * @summary Request faucet funds for onchain address. @@ -2665,6 +3430,18 @@ export interface AddressesApiInterface { */ createAddress(walletId: string, createAddressRequest?: CreateAddressRequest, options?: RawAxiosRequestConfig): AxiosPromise
; + /** + * Create a new payload signature with an address. + * @summary Create a new payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address to sign the payload with. + * @param {CreatePayloadSignatureRequest} [createPayloadSignatureRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AddressesApiInterface + */ + createPayloadSignature(walletId: string, addressId: string, createPayloadSignatureRequest?: CreatePayloadSignatureRequest, options?: RawAxiosRequestConfig): AxiosPromise; + /** * Get address * @summary Get address by onchain address @@ -2689,11 +3466,23 @@ export interface AddressesApiInterface { getAddressBalance(walletId: string, addressId: string, assetId: string, options?: RawAxiosRequestConfig): AxiosPromise; /** - * Get address balances - * @summary Get all balances for address - * @param {string} walletId The ID of the wallet to fetch the balances for - * @param {string} addressId The onchain address of the address that is being fetched. - * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * Get payload signature. + * @summary Get payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address that signed the payload. + * @param {string} payloadSignatureId The ID of the payload signature to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AddressesApiInterface + */ + getPayloadSignature(walletId: string, addressId: string, payloadSignatureId: string, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Get address balances + * @summary Get all balances for address + * @param {string} walletId The ID of the wallet to fetch the balances for + * @param {string} addressId The onchain address of the address that is being fetched. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AddressesApiInterface @@ -2712,6 +3501,19 @@ export interface AddressesApiInterface { */ listAddresses(walletId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise; + /** + * List payload signatures for an address. + * @summary List payload signatures for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address whose payload signatures to fetch. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AddressesApiInterface + */ + listPayloadSignatures(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise; + /** * Request faucet funds to be sent to onchain address. * @summary Request faucet funds for onchain address. @@ -2746,6 +3548,20 @@ export class AddressesApi extends BaseAPI implements AddressesApiInterface { return AddressesApiFp(this.configuration).createAddress(walletId, createAddressRequest, options).then((request) => request(this.axios, this.basePath)); } + /** + * Create a new payload signature with an address. + * @summary Create a new payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address to sign the payload with. + * @param {CreatePayloadSignatureRequest} [createPayloadSignatureRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AddressesApi + */ + public createPayloadSignature(walletId: string, addressId: string, createPayloadSignatureRequest?: CreatePayloadSignatureRequest, options?: RawAxiosRequestConfig) { + return AddressesApiFp(this.configuration).createPayloadSignature(walletId, addressId, createPayloadSignatureRequest, options).then((request) => request(this.axios, this.basePath)); + } + /** * Get address * @summary Get address by onchain address @@ -2773,6 +3589,20 @@ export class AddressesApi extends BaseAPI implements AddressesApiInterface { return AddressesApiFp(this.configuration).getAddressBalance(walletId, addressId, assetId, options).then((request) => request(this.axios, this.basePath)); } + /** + * Get payload signature. + * @summary Get payload signature. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address that signed the payload. + * @param {string} payloadSignatureId The ID of the payload signature to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AddressesApi + */ + public getPayloadSignature(walletId: string, addressId: string, payloadSignatureId: string, options?: RawAxiosRequestConfig) { + return AddressesApiFp(this.configuration).getPayloadSignature(walletId, addressId, payloadSignatureId, options).then((request) => request(this.axios, this.basePath)); + } + /** * Get address balances * @summary Get all balances for address @@ -2801,6 +3631,21 @@ export class AddressesApi extends BaseAPI implements AddressesApiInterface { return AddressesApiFp(this.configuration).listAddresses(walletId, limit, page, options).then((request) => request(this.axios, this.basePath)); } + /** + * List payload signatures for an address. + * @summary List payload signatures for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The onchain address of the address whose payload signatures to fetch. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AddressesApi + */ + public listPayloadSignatures(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig) { + return AddressesApiFp(this.configuration).listPayloadSignatures(walletId, addressId, limit, page, options).then((request) => request(this.axios, this.basePath)); + } + /** * Request faucet funds to be sent to onchain address. * @summary Request faucet funds for onchain address. @@ -3001,28 +3846,330 @@ export const ContractEventsApiAxiosParamCreator = function (configuration?: Conf const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - if (protocolName !== undefined) { - localVarQueryParameter['protocol_name'] = protocolName; - } - - if (contractName !== undefined) { - localVarQueryParameter['contract_name'] = contractName; - } - - if (eventName !== undefined) { - localVarQueryParameter['event_name'] = eventName; - } - - if (fromBlockHeight !== undefined) { - localVarQueryParameter['from_block_height'] = fromBlockHeight; - } - - if (toBlockHeight !== undefined) { - localVarQueryParameter['to_block_height'] = toBlockHeight; + if (protocolName !== undefined) { + localVarQueryParameter['protocol_name'] = protocolName; + } + + if (contractName !== undefined) { + localVarQueryParameter['contract_name'] = contractName; + } + + if (eventName !== undefined) { + localVarQueryParameter['event_name'] = eventName; + } + + if (fromBlockHeight !== undefined) { + localVarQueryParameter['from_block_height'] = fromBlockHeight; + } + + if (toBlockHeight !== undefined) { + localVarQueryParameter['to_block_height'] = toBlockHeight; + } + + if (nextPage !== undefined) { + localVarQueryParameter['next_page'] = nextPage; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * ContractEventsApi - functional programming interface + * @export + */ +export const ContractEventsApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = ContractEventsApiAxiosParamCreator(configuration) + return { + /** + * Retrieve events for a specific contract + * @summary Get contract events + * @param {string} networkId Unique identifier for the blockchain network + * @param {string} protocolName Case-sensitive name of the blockchain protocol + * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) + * @param {string} contractName Case-sensitive name of the specific contract within the project + * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs + * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) + * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) + * @param {string} [nextPage] Pagination token for retrieving the next set of results + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listContractEvents(networkId, protocolName, contractAddress, contractName, eventName, fromBlockHeight, toBlockHeight, nextPage, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ContractEventsApi.listContractEvents']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * ContractEventsApi - factory interface + * @export + */ +export const ContractEventsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = ContractEventsApiFp(configuration) + return { + /** + * Retrieve events for a specific contract + * @summary Get contract events + * @param {string} networkId Unique identifier for the blockchain network + * @param {string} protocolName Case-sensitive name of the blockchain protocol + * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) + * @param {string} contractName Case-sensitive name of the specific contract within the project + * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs + * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) + * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) + * @param {string} [nextPage] Pagination token for retrieving the next set of results + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: any): AxiosPromise { + return localVarFp.listContractEvents(networkId, protocolName, contractAddress, contractName, eventName, fromBlockHeight, toBlockHeight, nextPage, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * ContractEventsApi - interface + * @export + * @interface ContractEventsApi + */ +export interface ContractEventsApiInterface { + /** + * Retrieve events for a specific contract + * @summary Get contract events + * @param {string} networkId Unique identifier for the blockchain network + * @param {string} protocolName Case-sensitive name of the blockchain protocol + * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) + * @param {string} contractName Case-sensitive name of the specific contract within the project + * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs + * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) + * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) + * @param {string} [nextPage] Pagination token for retrieving the next set of results + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractEventsApiInterface + */ + listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: RawAxiosRequestConfig): AxiosPromise; + +} + +/** + * ContractEventsApi - object-oriented interface + * @export + * @class ContractEventsApi + * @extends {BaseAPI} + */ +export class ContractEventsApi extends BaseAPI implements ContractEventsApiInterface { + /** + * Retrieve events for a specific contract + * @summary Get contract events + * @param {string} networkId Unique identifier for the blockchain network + * @param {string} protocolName Case-sensitive name of the blockchain protocol + * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) + * @param {string} contractName Case-sensitive name of the specific contract within the project + * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs + * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) + * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) + * @param {string} [nextPage] Pagination token for retrieving the next set of results + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractEventsApi + */ + public listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: RawAxiosRequestConfig) { + return ContractEventsApiFp(this.configuration).listContractEvents(networkId, protocolName, contractAddress, contractName, eventName, fromBlockHeight, toBlockHeight, nextPage, options).then((request) => request(this.axios, this.basePath)); + } +} + + + +/** + * ContractInvocationsApi - axios parameter creator + * @export + */ +export const ContractInvocationsApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Broadcast a contract invocation. + * @summary Broadcast a contract invocation. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to broadcast. + * @param {BroadcastContractInvocationRequest} broadcastContractInvocationRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + broadcastContractInvocation: async (walletId: string, addressId: string, contractInvocationId: string, broadcastContractInvocationRequest: BroadcastContractInvocationRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('broadcastContractInvocation', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('broadcastContractInvocation', 'addressId', addressId) + // verify required parameter 'contractInvocationId' is not null or undefined + assertParamExists('broadcastContractInvocation', 'contractInvocationId', contractInvocationId) + // verify required parameter 'broadcastContractInvocationRequest' is not null or undefined + assertParamExists('broadcastContractInvocation', 'broadcastContractInvocationRequest', broadcastContractInvocationRequest) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/contract_invocations/{contract_invocation_id}/broadcast` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))) + .replace(`{${"contract_invocation_id"}}`, encodeURIComponent(String(contractInvocationId))); + // 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; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(broadcastContractInvocationRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Create a new contract invocation. + * @summary Create a new contract invocation for an address. + * @param {string} walletId The ID of the wallet the source address belongs to. + * @param {string} addressId The ID of the address to invoke the contract from. + * @param {CreateContractInvocationRequest} createContractInvocationRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createContractInvocation: async (walletId: string, addressId: string, createContractInvocationRequest: CreateContractInvocationRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('createContractInvocation', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('createContractInvocation', 'addressId', addressId) + // verify required parameter 'createContractInvocationRequest' is not null or undefined + assertParamExists('createContractInvocation', 'createContractInvocationRequest', createContractInvocationRequest) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/contract_invocations` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))); + // 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; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createContractInvocationRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get a contract invocation by ID. + * @summary Get a contract invocation by ID. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getContractInvocation: async (walletId: string, addressId: string, contractInvocationId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('getContractInvocation', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('getContractInvocation', 'addressId', addressId) + // verify required parameter 'contractInvocationId' is not null or undefined + assertParamExists('getContractInvocation', 'contractInvocationId', contractInvocationId) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/contract_invocations/{contract_invocation_id}` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))) + .replace(`{${"contract_invocation_id"}}`, encodeURIComponent(String(contractInvocationId))); + // 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: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List contract invocations for an address. + * @summary List contract invocations for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address to list contract invocations for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listContractInvocations: async (walletId: string, addressId: string, limit?: number, page?: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'walletId' is not null or undefined + assertParamExists('listContractInvocations', 'walletId', walletId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('listContractInvocations', 'addressId', addressId) + const localVarPath = `/v1/wallets/{wallet_id}/addresses/{address_id}/contract_invocations` + .replace(`{${"wallet_id"}}`, encodeURIComponent(String(walletId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))); + // 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: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (limit !== undefined) { + localVarQueryParameter['limit'] = limit; } - if (nextPage !== undefined) { - localVarQueryParameter['next_page'] = nextPage; + if (page !== undefined) { + localVarQueryParameter['page'] = page; } @@ -3040,111 +4187,258 @@ export const ContractEventsApiAxiosParamCreator = function (configuration?: Conf }; /** - * ContractEventsApi - functional programming interface + * ContractInvocationsApi - functional programming interface * @export */ -export const ContractEventsApiFp = function(configuration?: Configuration) { - const localVarAxiosParamCreator = ContractEventsApiAxiosParamCreator(configuration) +export const ContractInvocationsApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = ContractInvocationsApiAxiosParamCreator(configuration) return { /** - * Retrieve events for a specific contract - * @summary Get contract events - * @param {string} networkId Unique identifier for the blockchain network - * @param {string} protocolName Case-sensitive name of the blockchain protocol - * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) - * @param {string} contractName Case-sensitive name of the specific contract within the project - * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs - * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) - * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) - * @param {string} [nextPage] Pagination token for retrieving the next set of results + * Broadcast a contract invocation. + * @summary Broadcast a contract invocation. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to broadcast. + * @param {BroadcastContractInvocationRequest} broadcastContractInvocationRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.listContractEvents(networkId, protocolName, contractAddress, contractName, eventName, fromBlockHeight, toBlockHeight, nextPage, options); + async broadcastContractInvocation(walletId: string, addressId: string, contractInvocationId: string, broadcastContractInvocationRequest: BroadcastContractInvocationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.broadcastContractInvocation(walletId, addressId, contractInvocationId, broadcastContractInvocationRequest, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['ContractEventsApi.listContractEvents']?.[localVarOperationServerIndex]?.url; + const localVarOperationServerBasePath = operationServerMap['ContractInvocationsApi.broadcastContractInvocation']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Create a new contract invocation. + * @summary Create a new contract invocation for an address. + * @param {string} walletId The ID of the wallet the source address belongs to. + * @param {string} addressId The ID of the address to invoke the contract from. + * @param {CreateContractInvocationRequest} createContractInvocationRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createContractInvocation(walletId: string, addressId: string, createContractInvocationRequest: CreateContractInvocationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createContractInvocation(walletId, addressId, createContractInvocationRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ContractInvocationsApi.createContractInvocation']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get a contract invocation by ID. + * @summary Get a contract invocation by ID. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getContractInvocation(walletId: string, addressId: string, contractInvocationId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getContractInvocation(walletId, addressId, contractInvocationId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ContractInvocationsApi.getContractInvocation']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List contract invocations for an address. + * @summary List contract invocations for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address to list contract invocations for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listContractInvocations(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listContractInvocations(walletId, addressId, limit, page, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ContractInvocationsApi.listContractInvocations']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, } }; /** - * ContractEventsApi - factory interface + * ContractInvocationsApi - factory interface * @export */ -export const ContractEventsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { - const localVarFp = ContractEventsApiFp(configuration) +export const ContractInvocationsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = ContractInvocationsApiFp(configuration) return { /** - * Retrieve events for a specific contract - * @summary Get contract events - * @param {string} networkId Unique identifier for the blockchain network - * @param {string} protocolName Case-sensitive name of the blockchain protocol - * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) - * @param {string} contractName Case-sensitive name of the specific contract within the project - * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs - * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) - * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) - * @param {string} [nextPage] Pagination token for retrieving the next set of results + * Broadcast a contract invocation. + * @summary Broadcast a contract invocation. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to broadcast. + * @param {BroadcastContractInvocationRequest} broadcastContractInvocationRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: any): AxiosPromise { - return localVarFp.listContractEvents(networkId, protocolName, contractAddress, contractName, eventName, fromBlockHeight, toBlockHeight, nextPage, options).then((request) => request(axios, basePath)); + broadcastContractInvocation(walletId: string, addressId: string, contractInvocationId: string, broadcastContractInvocationRequest: BroadcastContractInvocationRequest, options?: any): AxiosPromise { + return localVarFp.broadcastContractInvocation(walletId, addressId, contractInvocationId, broadcastContractInvocationRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Create a new contract invocation. + * @summary Create a new contract invocation for an address. + * @param {string} walletId The ID of the wallet the source address belongs to. + * @param {string} addressId The ID of the address to invoke the contract from. + * @param {CreateContractInvocationRequest} createContractInvocationRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createContractInvocation(walletId: string, addressId: string, createContractInvocationRequest: CreateContractInvocationRequest, options?: any): AxiosPromise { + return localVarFp.createContractInvocation(walletId, addressId, createContractInvocationRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Get a contract invocation by ID. + * @summary Get a contract invocation by ID. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getContractInvocation(walletId: string, addressId: string, contractInvocationId: string, options?: any): AxiosPromise { + return localVarFp.getContractInvocation(walletId, addressId, contractInvocationId, options).then((request) => request(axios, basePath)); + }, + /** + * List contract invocations for an address. + * @summary List contract invocations for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address to list contract invocations for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listContractInvocations(walletId: string, addressId: string, limit?: number, page?: string, options?: any): AxiosPromise { + return localVarFp.listContractInvocations(walletId, addressId, limit, page, options).then((request) => request(axios, basePath)); }, }; }; /** - * ContractEventsApi - interface + * ContractInvocationsApi - interface * @export - * @interface ContractEventsApi + * @interface ContractInvocationsApi */ -export interface ContractEventsApiInterface { +export interface ContractInvocationsApiInterface { /** - * Retrieve events for a specific contract - * @summary Get contract events - * @param {string} networkId Unique identifier for the blockchain network - * @param {string} protocolName Case-sensitive name of the blockchain protocol - * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) - * @param {string} contractName Case-sensitive name of the specific contract within the project - * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs - * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) - * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) - * @param {string} [nextPage] Pagination token for retrieving the next set of results + * Broadcast a contract invocation. + * @summary Broadcast a contract invocation. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to broadcast. + * @param {BroadcastContractInvocationRequest} broadcastContractInvocationRequest * @param {*} [options] Override http request option. * @throws {RequiredError} - * @memberof ContractEventsApiInterface + * @memberof ContractInvocationsApiInterface */ - listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: RawAxiosRequestConfig): AxiosPromise; + broadcastContractInvocation(walletId: string, addressId: string, contractInvocationId: string, broadcastContractInvocationRequest: BroadcastContractInvocationRequest, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Create a new contract invocation. + * @summary Create a new contract invocation for an address. + * @param {string} walletId The ID of the wallet the source address belongs to. + * @param {string} addressId The ID of the address to invoke the contract from. + * @param {CreateContractInvocationRequest} createContractInvocationRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractInvocationsApiInterface + */ + createContractInvocation(walletId: string, addressId: string, createContractInvocationRequest: CreateContractInvocationRequest, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Get a contract invocation by ID. + * @summary Get a contract invocation by ID. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractInvocationsApiInterface + */ + getContractInvocation(walletId: string, addressId: string, contractInvocationId: string, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * List contract invocations for an address. + * @summary List contract invocations for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address to list contract invocations for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractInvocationsApiInterface + */ + listContractInvocations(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise; } /** - * ContractEventsApi - object-oriented interface + * ContractInvocationsApi - object-oriented interface * @export - * @class ContractEventsApi + * @class ContractInvocationsApi * @extends {BaseAPI} */ -export class ContractEventsApi extends BaseAPI implements ContractEventsApiInterface { +export class ContractInvocationsApi extends BaseAPI implements ContractInvocationsApiInterface { /** - * Retrieve events for a specific contract - * @summary Get contract events - * @param {string} networkId Unique identifier for the blockchain network - * @param {string} protocolName Case-sensitive name of the blockchain protocol - * @param {string} contractAddress EVM address of the smart contract (42 characters, including \'0x\', in lowercase) - * @param {string} contractName Case-sensitive name of the specific contract within the project - * @param {string} eventName Case-sensitive name of the event to filter for in the contract\'s logs - * @param {number} fromBlockHeight Lower bound of the block range to query (inclusive) - * @param {number} toBlockHeight Upper bound of the block range to query (inclusive) - * @param {string} [nextPage] Pagination token for retrieving the next set of results + * Broadcast a contract invocation. + * @summary Broadcast a contract invocation. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to broadcast. + * @param {BroadcastContractInvocationRequest} broadcastContractInvocationRequest * @param {*} [options] Override http request option. * @throws {RequiredError} - * @memberof ContractEventsApi + * @memberof ContractInvocationsApi */ - public listContractEvents(networkId: string, protocolName: string, contractAddress: string, contractName: string, eventName: string, fromBlockHeight: number, toBlockHeight: number, nextPage?: string, options?: RawAxiosRequestConfig) { - return ContractEventsApiFp(this.configuration).listContractEvents(networkId, protocolName, contractAddress, contractName, eventName, fromBlockHeight, toBlockHeight, nextPage, options).then((request) => request(this.axios, this.basePath)); + public broadcastContractInvocation(walletId: string, addressId: string, contractInvocationId: string, broadcastContractInvocationRequest: BroadcastContractInvocationRequest, options?: RawAxiosRequestConfig) { + return ContractInvocationsApiFp(this.configuration).broadcastContractInvocation(walletId, addressId, contractInvocationId, broadcastContractInvocationRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Create a new contract invocation. + * @summary Create a new contract invocation for an address. + * @param {string} walletId The ID of the wallet the source address belongs to. + * @param {string} addressId The ID of the address to invoke the contract from. + * @param {CreateContractInvocationRequest} createContractInvocationRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractInvocationsApi + */ + public createContractInvocation(walletId: string, addressId: string, createContractInvocationRequest: CreateContractInvocationRequest, options?: RawAxiosRequestConfig) { + return ContractInvocationsApiFp(this.configuration).createContractInvocation(walletId, addressId, createContractInvocationRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get a contract invocation by ID. + * @summary Get a contract invocation by ID. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address the contract invocation belongs to. + * @param {string} contractInvocationId The ID of the contract invocation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractInvocationsApi + */ + public getContractInvocation(walletId: string, addressId: string, contractInvocationId: string, options?: RawAxiosRequestConfig) { + return ContractInvocationsApiFp(this.configuration).getContractInvocation(walletId, addressId, contractInvocationId, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List contract invocations for an address. + * @summary List contract invocations for an address. + * @param {string} walletId The ID of the wallet the address belongs to. + * @param {string} addressId The ID of the address to list contract invocations for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ContractInvocationsApi + */ + public listContractInvocations(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig) { + return ContractInvocationsApiFp(this.configuration).listContractInvocations(walletId, addressId, limit, page, options).then((request) => request(this.axios, this.basePath)); } } @@ -3241,6 +4535,54 @@ export const ExternalAddressesApiAxiosParamCreator = function (configuration?: C + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List all transactions that interact with the address. + * @summary List transactions for an address. + * @param {string} networkId The ID of the blockchain network + * @param {string} addressId The ID of the address to fetch the transactions for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listAddressTransactions: async (networkId: string, addressId: string, limit?: number, page?: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'networkId' is not null or undefined + assertParamExists('listAddressTransactions', 'networkId', networkId) + // verify required parameter 'addressId' is not null or undefined + assertParamExists('listAddressTransactions', 'addressId', addressId) + const localVarPath = `/v1/networks/{network_id}/addresses/{address_id}/transactions` + .replace(`{${"network_id"}}`, encodeURIComponent(String(networkId))) + .replace(`{${"address_id"}}`, encodeURIComponent(String(addressId))); + // 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: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (limit !== undefined) { + localVarQueryParameter['limit'] = limit; + } + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -3378,6 +4720,22 @@ export const ExternalAddressesApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['ExternalAddressesApi.listAddressHistoricalBalance']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * List all transactions that interact with the address. + * @summary List transactions for an address. + * @param {string} networkId The ID of the blockchain network + * @param {string} addressId The ID of the address to fetch the transactions for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listAddressTransactions(networkId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listAddressTransactions(networkId, addressId, limit, page, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ExternalAddressesApi.listAddressTransactions']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * List all of the balances of an external address * @summary Get the balances of an external address @@ -3444,6 +4802,19 @@ export const ExternalAddressesApiFactory = function (configuration?: Configurati listAddressHistoricalBalance(networkId: string, addressId: string, assetId: string, limit?: number, page?: string, options?: any): AxiosPromise { return localVarFp.listAddressHistoricalBalance(networkId, addressId, assetId, limit, page, options).then((request) => request(axios, basePath)); }, + /** + * List all transactions that interact with the address. + * @summary List transactions for an address. + * @param {string} networkId The ID of the blockchain network + * @param {string} addressId The ID of the address to fetch the transactions for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listAddressTransactions(networkId: string, addressId: string, limit?: number, page?: string, options?: any): AxiosPromise { + return localVarFp.listAddressTransactions(networkId, addressId, limit, page, options).then((request) => request(axios, basePath)); + }, /** * List all of the balances of an external address * @summary Get the balances of an external address @@ -3503,6 +4874,19 @@ export interface ExternalAddressesApiInterface { */ listAddressHistoricalBalance(networkId: string, addressId: string, assetId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise; + /** + * List all transactions that interact with the address. + * @summary List transactions for an address. + * @param {string} networkId The ID of the blockchain network + * @param {string} addressId The ID of the address to fetch the transactions for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ExternalAddressesApiInterface + */ + listAddressTransactions(networkId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise; + /** * List all of the balances of an external address * @summary Get the balances of an external address @@ -3566,6 +4950,21 @@ export class ExternalAddressesApi extends BaseAPI implements ExternalAddressesAp return ExternalAddressesApiFp(this.configuration).listAddressHistoricalBalance(networkId, addressId, assetId, limit, page, options).then((request) => request(this.axios, this.basePath)); } + /** + * List all transactions that interact with the address. + * @summary List transactions for an address. + * @param {string} networkId The ID of the blockchain network + * @param {string} addressId The ID of the address to fetch the transactions for. + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ExternalAddressesApi + */ + public listAddressTransactions(networkId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig) { + return ExternalAddressesApiFp(this.configuration).listAddressTransactions(networkId, addressId, limit, page, options).then((request) => request(this.axios, this.basePath)); + } + /** * List all of the balances of an external address * @summary Get the balances of an external address diff --git a/src/client/base.ts b/src/client/base.ts index c9d00c23..b8ca5b52 100644 --- a/src/client/base.ts +++ b/src/client/base.ts @@ -5,7 +5,7 @@ * This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. * * The version of the OpenAPI document: 0.0.1-alpha - * Contact: yuga.cohler@coinbase.com + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech diff --git a/src/client/common.ts b/src/client/common.ts index ed8926ba..eb622219 100644 --- a/src/client/common.ts +++ b/src/client/common.ts @@ -5,7 +5,7 @@ * This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. * * The version of the OpenAPI document: 0.0.1-alpha - * Contact: yuga.cohler@coinbase.com + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -89,17 +89,17 @@ function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: an if (typeof parameter === "object") { if (Array.isArray(parameter)) { (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); - } + } else { - Object.keys(parameter).forEach(currentKey => + Object.keys(parameter).forEach(currentKey => setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) ); } - } + } else { if (urlSearchParams.has(key)) { urlSearchParams.append(key, parameter); - } + } else { urlSearchParams.set(key, parameter); } diff --git a/src/client/configuration.ts b/src/client/configuration.ts index 3b1c5f27..1fc8a134 100644 --- a/src/client/configuration.ts +++ b/src/client/configuration.ts @@ -5,7 +5,7 @@ * This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. * * The version of the OpenAPI document: 0.0.1-alpha - * Contact: yuga.cohler@coinbase.com + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech diff --git a/src/client/index.ts b/src/client/index.ts index 4cb285eb..814de5aa 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -5,7 +5,7 @@ * This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. * * The version of the OpenAPI document: 0.0.1-alpha - * Contact: yuga.cohler@coinbase.com + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech diff --git a/src/coinbase/address.ts b/src/coinbase/address.ts index 5a00c034..da7c0732 100644 --- a/src/coinbase/address.ts +++ b/src/coinbase/address.ts @@ -10,11 +10,14 @@ import { StakeOptionsMode, ListHistoricalBalancesResult, ListHistoricalBalancesOptions, + ListTransactionsOptions, + ListTransactionsResult, } from "./types"; import { formatDate, getWeekBackDate } from "./utils"; import { StakingRewardFormat } from "../client"; import { StakingReward } from "./staking_reward"; import { StakingBalance } from "./staking_balance"; +import { Transaction } from "./transaction"; /** * A representation of a blockchain address, which is a user-controlled account on a network. @@ -153,6 +156,38 @@ export class Address { }; } + /** + * Returns the transactions of the address. + * + * @param options - The options to list transactions. + * @param options.limit - A limit on the number of objects to be returned. Limit can range between 1 and 25, and the default is 10. + * @param options.page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @returns The list of historical balance of the asset and next page token. + */ + public async listTransactions({ + limit, + page, + }: ListTransactionsOptions): Promise { + const txnList: Transaction[] = []; + + const response = await Coinbase.apiClients.externalAddress!.listAddressTransactions( + this.getNetworkId(), + this.getId(), + limit ? limit : undefined, + page ? page : undefined, + ); + + response.data.data.forEach(transactionModel => { + const transaction = new Transaction(transactionModel); + txnList.push(transaction); + }); + + return { + transactions: txnList, + nextPageToken: response.data.next_page, + }; + } + /** * Lists the staking rewards for the address. * diff --git a/src/coinbase/address/wallet_address.ts b/src/coinbase/address/wallet_address.ts index 42397a56..fa001357 100644 --- a/src/coinbase/address/wallet_address.ts +++ b/src/coinbase/address/wallet_address.ts @@ -7,18 +7,19 @@ import { Coinbase } from "../coinbase"; import { ArgumentError } from "../errors"; import { Trade } from "../trade"; import { Transfer } from "../transfer"; +import { ContractInvocation } from "../contract_invocation"; import { Amount, CreateTransferOptions, CreateTradeOptions, + CreateContractInvocationOptions, Destination, - TransferStatus, - TransactionStatus, StakeOptionsMode, } from "../types"; import { delay } from "../utils"; import { Wallet as WalletClass } from "../wallet"; import { StakingOperation } from "../staking_operation"; +import { PayloadSignature } from "../payload_signature"; /** * A representation of a blockchain address, which is a wallet-controlled account on a network. @@ -75,6 +76,19 @@ export class WalletAddress extends Address { this.key = key; } + /** + * Exports the Address's private key to a hex string. + * + * @returns The Address's private key as a hex string. + */ + public export() { + if (this.key === undefined) { + throw new Error("Private key is not set"); + } + + return this.key.privateKey; + } + /** * Returns whether the Address has a private key backing it to sign transactions. * @@ -252,6 +266,66 @@ export class WalletAddress extends Address { return trade; } + /** + * Invokes a contract with the given data. + * + * @param options - The options to invoke the contract + * @param options.contractAddress - The address of the contract the method will be invoked on. + * @param options.method - The method to invoke on the contract. + * @param options.abi - The ABI of the contract. + * @param options.args - The arguments to pass to the contract method invocation. + * The keys should be the argument names and the values should be the argument values. + * @returns The ContractInvocation object. + * @throws {APIError} if the API request to create a contract invocation fails. + */ + public async invokeContract( + options: CreateContractInvocationOptions, + ): Promise { + if (!Coinbase.useServerSigner && !this.key) { + throw new Error("Cannot invoke contract from address without private key loaded"); + } + + const contractInvocation = await this.createContractInvocation(options); + + if (Coinbase.useServerSigner) { + return contractInvocation; + } + + await contractInvocation.sign(this.getSigner()); + await contractInvocation.broadcast(); + + return contractInvocation; + } + + /** + * Creates a contract invocation model for the specified contract address, method, and arguments. + * The ABI object must be specified if the contract is not a known contract. + * + * @param amount - The amount of the Asset to send. + * @param fromAsset - The Asset to trade from. + * @param toAsset - The Asset to trade to. + * @returns A promise that resolves to a Trade object representing the new trade. + */ + private async createContractInvocation({ + abi, + args, + contractAddress, + method, + }: CreateContractInvocationOptions): Promise { + const resp = await Coinbase.apiClients.contractInvocation!.createContractInvocation( + this.getWalletId(), + this.getId(), + { + method, + abi: JSON.stringify(abi), + contract_address: contractAddress, + args: JSON.stringify(args), + }, + ); + + return ContractInvocation.fromModel(resp?.data); + } + /** * Creates a staking operation to stake. * @@ -366,6 +440,92 @@ export class WalletAddress extends Address { ); } + /** + * Creates a Payload Signature. + * + * @param unsignedPayload - The Unsigned Payload to sign. + * @returns A promise that resolves to the Payload Signature object. + * @throws {APIError} if the API request to create a Payload Signature fails. + * @throws {Error} if the address does not have a private key loaded or an associated Server-Signer. + */ + public async createPayloadSignature(unsignedPayload: string): Promise { + if (!Coinbase.useServerSigner && !this.key) { + throw new Error("Cannot sign payload with address without private key loaded"); + } + + let signature: undefined | string = undefined; + if (!Coinbase.useServerSigner) { + signature = this.key!.signingKey.sign(unsignedPayload).serialized; + } + + const createPayloadSignatureRequest = { + unsigned_payload: unsignedPayload, + signature, + }; + + const response = await Coinbase.apiClients.address!.createPayloadSignature( + this.getWalletId(), + this.getId(), + createPayloadSignatureRequest, + ); + + const payloadSignature = new PayloadSignature(response.data); + + return payloadSignature; + } + + /** + * Gets a Payload Signature. + * + * @param payloadSignatureId - The ID of the Payload Signature to fetch. + * @returns A promise that resolves to the Payload Signature object. + * @throws {APIError} if the API request to get the Payload Signature fails. + */ + public async getPayloadSignature(payloadSignatureId: string): Promise { + const response = await Coinbase.apiClients.address!.getPayloadSignature( + this.getWalletId(), + this.getId(), + payloadSignatureId, + ); + + const payloadSignature = new PayloadSignature(response.data); + + return payloadSignature; + } + + /** + * Lists all the Payload Signatures associated with the Address. + * + * @returns A promise that resolves to the list of Payload Signature objects. + * @throws {APIError} if the API request to list the Payload Signatures fails. + */ + public async listPayloadSignatures(): Promise { + const payloadSignatures: PayloadSignature[] = []; + const queue: string[] = [""]; + + while (queue.length > 0) { + const page = queue.shift(); + const response = await Coinbase.apiClients.address!.listPayloadSignatures( + this.model.wallet_id, + this.model.address_id, + 100, + page?.length ? page : undefined, + ); + + response.data.data.forEach(payloadSignatureModel => { + payloadSignatures.push(new PayloadSignature(payloadSignatureModel)); + }); + + if (response.data.has_more) { + if (response.data.next_page) { + queue.push(response.data.next_page); + } + } + } + + return payloadSignatures; + } + /** * Returns the address and network ID of the given destination. * diff --git a/src/coinbase/coinbase.ts b/src/coinbase/coinbase.ts index 0620ae43..fec1c1a4 100644 --- a/src/coinbase/coinbase.ts +++ b/src/coinbase/coinbase.ts @@ -15,6 +15,7 @@ import { WebhooksApiFactory, NetworkIdentifier, ContractEventsApiFactory, + ContractInvocationsApiFactory, } from "../client"; import { BASE_PATH } from "./../client/base"; import { Configuration } from "./../client/configuration"; @@ -128,6 +129,11 @@ export class Coinbase { Coinbase.apiClients.validator = ValidatorsApiFactory(config, basePath, axiosInstance); Coinbase.apiClients.asset = AssetsApiFactory(config, basePath, axiosInstance); Coinbase.apiClients.webhook = WebhooksApiFactory(config, basePath, axiosInstance); + Coinbase.apiClients.contractInvocation = ContractInvocationsApiFactory( + config, + basePath, + axiosInstance, + ); Coinbase.apiClients.externalAddress = ExternalAddressesApiFactory( config, basePath, diff --git a/src/coinbase/contract_invocation.ts b/src/coinbase/contract_invocation.ts new file mode 100644 index 00000000..82b12614 --- /dev/null +++ b/src/coinbase/contract_invocation.ts @@ -0,0 +1,257 @@ +import { TransactionStatus } from "./types"; +import { Transaction } from "./transaction"; +import { Coinbase } from "./coinbase"; +import { ContractInvocation as ContractInvocationModel } from "../client/api"; +import { ethers } from "ethers"; +import { delay } from "./utils"; +import { TimeoutError } from "./errors"; + +/** + * A representation of a ContractInvocation, which moves an Amount of an Asset from + * a user-controlled Wallet to another Address. The fee is assumed to be paid + * in the native Asset of the Network. + */ +export class ContractInvocation { + private model: ContractInvocationModel; + + /** + * Private constructor to prevent direct instantiation outside of the factory methods. + * + * @ignore + * @param contractInvocationModel - The ContractInvocation model. + * @hideconstructor + */ + private constructor(contractInvocationModel: ContractInvocationModel) { + if (!contractInvocationModel) { + throw new Error("ContractInvocation model cannot be empty"); + } + this.model = contractInvocationModel; + } + + /** + * Converts a ContractInvocationModel into a ContractInvocation object. + * + * @param contractInvocationModel - The ContractInvocation model object. + * @returns The ContractInvocation object. + */ + public static fromModel(contractInvocationModel: ContractInvocationModel): ContractInvocation { + return new ContractInvocation(contractInvocationModel); + } + + /** + * Returns the ID of the ContractInvocation. + * + * @returns The ContractInvocation ID. + */ + public getId(): string { + return this.model.contract_invocation_id; + } + + /** + * Returns the Network ID of the ContractInvocation. + * + * @returns The Network ID. + */ + public getNetworkId(): string { + return this.model.network_id; + } + + /** + * Returns the Wallet ID of the ContractInvocation. + * + * @returns The Wallet ID. + */ + public getWalletId(): string { + return this.model.wallet_id; + } + + /** + * Returns the From Address ID of the ContractInvocation. + * + * @returns The From Address ID. + */ + public getFromAddressId(): string { + return this.model.address_id; + } + + /** + * Returns the Destination Address ID of the ContractInvocation. + * + * @returns The Destination Address ID. + */ + public getContractAddressId(): string { + return this.model.contract_address; + } + + /** + * Returns the Method of the ContractInvocation. + * + * @returns The Method. + */ + public getMethod(): string { + return this.model.method; + } + + /** + * Returns the Arguments of the ContractInvocation. + * + * @returns {object} The arguments object passed to the contract invocation. + * The key is the argument name and the value is the argument value. + */ + public getArgs(): object { + return JSON.parse(this.model.args); + } + + /** + * Returns the ABI of the ContractInvocation, if specified. + * + * @returns The ABI as an object, or undefined if not available. + */ + public getAbi(): object | undefined { + if (!this.model.abi) return undefined; + + return JSON.parse(this.model.abi); + } + + /** + * Returns the Transaction Hash of the ContractInvocation. + * + * @returns The Transaction Hash as a Hex string, or undefined if not yet available. + */ + public getTransactionHash(): string | undefined { + return this.getTransaction().getTransactionHash(); + } + + /** + * Returns the Transaction of the ContractInvocation. + * + * @returns The ethers.js Transaction object. + * @throws (InvalidUnsignedPayload) If the Unsigned Payload is invalid. + */ + public getRawTransaction(): ethers.Transaction { + return this.getTransaction().rawTransaction(); + } + + /** + * Signs the ContractInvocation with the provided key and returns the hex signature + * required for broadcasting the ContractInvocation. + * + * @param key - The key to sign the ContractInvocation with + * @returns The hex-encoded signed payload + */ + async sign(key: ethers.Wallet): Promise { + return this.getTransaction().sign(key); + } + + /** + * Returns the Status of the ContractInvocation. + * + * @returns The Status of the ContractInvocation. + */ + public getStatus(): TransactionStatus | undefined { + return this.getTransaction().getStatus(); + } + + /** + * Returns the Transaction of the ContractInvocation. + * + * @returns The Transaction + */ + public getTransaction(): Transaction { + return new Transaction(this.model.transaction); + } + + /** + * Returns the link to the Transaction on the blockchain explorer. + * + * @returns The link to the Transaction on the blockchain explorer. + */ + public getTransactionLink(): string { + return this.getTransaction().getTransactionLink(); + } + + /** + * Broadcasts the ContractInvocation to the Network. + * + * @returns The ContractInvocation object + * @throws {APIError} if the API request to broadcast a ContractInvocation fails. + */ + public async broadcast(): Promise { + if (!this.getTransaction()?.isSigned()) + throw new Error("Cannot broadcast unsigned ContractInvocation"); + + const broadcastContractInvocationRequest = { + signed_payload: this.getTransaction()!.getSignature()!, + }; + + const response = await Coinbase.apiClients.contractInvocation!.broadcastContractInvocation( + this.getWalletId(), + this.getFromAddressId(), + this.getId(), + broadcastContractInvocationRequest, + ); + + return ContractInvocation.fromModel(response.data); + } + + /** + * Waits for the ContractInvocation to be confirmed on the Network or fail on chain. + * Waits until the ContractInvocation is completed or failed on-chain by polling at the given interval. + * Raises an error if the ContractInvocation takes longer than the given timeout. + * + * @param options - The options to configure the wait function. + * @param options.intervalSeconds - The interval to check the status of the ContractInvocation. + * @param options.timeoutSeconds - The maximum time to wait for the ContractInvocation to be confirmed. + * + * @returns The ContractInvocation object in a terminal state. + * @throws {Error} if the ContractInvocation times out. + */ + public async wait({ + intervalSeconds = 0.2, + timeoutSeconds = 10, + } = {}): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutSeconds * 1000) { + await this.reload(); + + // If the ContractInvocation is in a terminal state, return the ContractInvocation. + const status = this.getStatus(); + if (status === TransactionStatus.COMPLETE || status === TransactionStatus.FAILED) { + return this; + } + + await delay(intervalSeconds); + } + + throw new TimeoutError("ContractInvocation timed out"); + } + + /** + * Reloads the ContractInvocation model with the latest data from the server. + * + * @throws {APIError} if the API request to get a ContractInvocation fails. + */ + public async reload(): Promise { + const result = await Coinbase.apiClients.contractInvocation!.getContractInvocation( + this.getWalletId(), + this.getFromAddressId(), + this.getId(), + ); + this.model = result?.data; + } + + /** + * Returns a string representation of the ContractInvocation. + * + * @returns The string representation of the ContractInvocation. + */ + public toString(): string { + return ( + `ContractInvocation{contractInvocationId: '${this.getId()}', networkId: '${this.getNetworkId()}', ` + + `fromAddressId: '${this.getFromAddressId()}', contractAddressId: '${this.getContractAddressId()}', ` + + `method: '${this.getMethod()}', args: '${this.getArgs()}', transactionHash: '${this.getTransactionHash()}', ` + + `transactionLink: '${this.getTransactionLink()}', status: '${this.getStatus()}'}` + ); + } +} diff --git a/src/coinbase/hash.ts b/src/coinbase/hash.ts new file mode 100644 index 00000000..f11afdbe --- /dev/null +++ b/src/coinbase/hash.ts @@ -0,0 +1,32 @@ +import { ethers } from "ethers"; +import { TypedDataDomain, TypedDataField } from "./types"; + +/** + * Computes the EIP-191 personal-sign message digest to sign. + * + * @returns The EIP-191 hash of the message as a string. + * @throws {Error} if the message cannot be hashed. + * @param message - The message to hash. + */ +export const hashMessage = (message: Uint8Array | string): string => { + return ethers.hashMessage(message); +}; + +/** + * Computes the hash of the EIP-712 compliant typed data message. + * + * @param domain - The domain parameters for the EIP-712 message, including the name, version, chainId, and verifying contract. + * @param types - The types definitions for the EIP-712 message, represented as a record of type names to their fields. + * @param value - The actual data object to hash, conforming to the types defined. + * + * @returns The EIP-712 hash of the typed data as a hex-encoded string. + * @throws {Error} if the typed data cannot be hashed. + */ +export const hashTypedDataMessage = ( + domain: TypedDataDomain, + types: Record>, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: Record, +): string => { + return ethers.TypedDataEncoder.hash(domain, types, value); +}; diff --git a/src/coinbase/payload_signature.ts b/src/coinbase/payload_signature.ts new file mode 100644 index 00000000..9556d045 --- /dev/null +++ b/src/coinbase/payload_signature.ts @@ -0,0 +1,154 @@ +import { PayloadSignature as PayloadSignatureModel } from "../client"; +import { PayloadSignatureStatus } from "./types"; +import { delay } from "./utils"; +import { TimeoutError } from "./errors"; +import { Coinbase } from "./coinbase"; + +/** + * A representation of a Payload Signature. + */ +export class PayloadSignature { + private model: PayloadSignatureModel; + + /** + * Constructs a Payload Signature. + * + * @class + * @param model - The underlying Payload Signature object. + */ + constructor(model: PayloadSignatureModel) { + if (!model) { + throw new Error("Invalid model type"); + } + this.model = model; + } + + /** + * Returns the ID of the Payload Signature. + * + * @returns The ID of the Payload Signature + */ + getId(): string { + return this.model.payload_signature_id; + } + + /** + * Returns the Wallet ID of the Payload Signature. + * + * @returns The Wallet ID + */ + getWalletId(): string { + return this.model.wallet_id; + } + + /** + * Returns the Address ID of the Payload Signature. + * + * @returns The Address ID + */ + getAddressId(): string { + return this.model.address_id; + } + + /** + * Returns the Unsigned Payload of the Payload Signature. + * + * @returns The Unsigned Payload + */ + getUnsignedPayload(): string { + return this.model.unsigned_payload; + } + + /** + * Returns the Signature of the Payload Signature. + * + * @returns The Signature + */ + getSignature(): string | undefined { + return this.model.signature; + } + + /** + * Returns the Status of the Payload Signature. + * + * @returns The Status + */ + getStatus(): PayloadSignatureStatus | undefined { + switch (this.model.status) { + case PayloadSignatureStatus.PENDING: + return PayloadSignatureStatus.PENDING; + case PayloadSignatureStatus.SIGNED: + return PayloadSignatureStatus.SIGNED; + case PayloadSignatureStatus.FAILED: + return PayloadSignatureStatus.FAILED; + default: + return undefined; + } + } + + /** + * Returns whether the Payload Signature is in a terminal State. + * + * @returns Whether the Payload Signature is in a terminal State + */ + isTerminalState(): boolean { + const status = this.getStatus(); + + if (!status) return false; + + return [PayloadSignatureStatus.SIGNED, PayloadSignatureStatus.FAILED].includes(status); + } + + /** + * Waits for the Payload Signature to be signed or for the signature operation to fail. + * + * @param options - The options to configure the wait function. + * @param options.intervalSeconds - The interval to check the status of the Payload Signature. + * @param options.timeoutSeconds - The maximum time to wait for the Payload Signature to be confirmed. + * + * @returns The Payload Signature object in a terminal state. + * @throws {Error} if the Payload Signature times out. + */ + public async wait({ + intervalSeconds = 0.2, + timeoutSeconds = 10, + } = {}): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutSeconds * 1000) { + await this.reload(); + + // If the Payload Signature is in a terminal state, return the Payload Signature. + if (this.isTerminalState()) { + return this; + } + + await delay(intervalSeconds); + } + + throw new TimeoutError("Payload Signature timed out"); + } + + /** + * Reloads the Payload Signature model with the latest data from the server. + * + * @throws {APIError} if the API request to get a Payload Signature fails. + */ + public async reload(): Promise { + const result = await Coinbase.apiClients.address!.getPayloadSignature( + this.getWalletId(), + this.getAddressId(), + this.getId(), + ); + this.model = result?.data; + } + + /** + * Returns a string representation of the Payload Signature. + * + * @returns A string representation of the Payload Signature. + */ + toString(): string { + return `PayloadSignature { status: '${this.getStatus()}', unsignedPayload: '${this.getUnsignedPayload()}', signature: ${this.getSignature()} }`; + } +} diff --git a/src/coinbase/transaction.ts b/src/coinbase/transaction.ts index b5ca7015..d7c6d3e3 100644 --- a/src/coinbase/transaction.ts +++ b/src/coinbase/transaction.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { Transaction as TransactionModel } from "../client/api"; +import { Transaction as TransactionModel, EthereumTransaction } from "../client/api"; import { TransactionStatus } from "./types"; import { parseUnsignedPayload } from "./utils"; @@ -88,6 +88,32 @@ export class Transaction { return this.model.to_address_id; } + /** + * Returns the Block Height where the Transaction is recorded. + * + * @returns The Block Height + */ + blockHeight(): string | undefined { + return this.model.block_height; + } + + /** + * Returns the Block Hash where the Transaction is recorded. + * + * @returns The Block Hash + */ + blockHash(): string | undefined { + return this.model.block_hash; + } + + /** + * Returns the Content of the Transaction. + * + * @returns The transaction content + */ + content(): EthereumTransaction | undefined { + return this.model.content; + } /** * Returns whether the Transaction is in a terminal State. * diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 2db606b8..9e3cd5b2 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -39,10 +39,21 @@ import { CreateWebhookRequest, UpdateWebhookRequest, ContractEventList, + CreatePayloadSignatureRequest, + PayloadSignature as PayloadSignatureModel, + PayloadSignatureList, + WebhookEventType, + WebhookEventFilter, + AddressTransactionList, + BroadcastContractInvocationRequest, + CreateContractInvocationRequest, + ContractInvocationList, + ContractInvocation as ContractInvocationModel, } from "./../client/api"; import { Address } from "./address"; import { Wallet } from "./wallet"; import { HistoricalBalance } from "./historical_balance"; +import { Transaction } from "./transaction"; export type AssetAPIClient = { /** @@ -305,6 +316,56 @@ export type AddressAPIClient = { createAddressRequest?: CreateAddressRequest, options?: AxiosRequestConfig, ): AxiosPromise; + + /** + * Create a new payload signature with an address. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The onchain address of the address to sign the payload with. + * @param CreatePayloadSignatureRequest - The payload signature creation request. + * @param options - Axios request options. + * @throws {APIError} If the request fails. + */ + createPayloadSignature( + walletId: string, + addressid: string, + createPayloadSignatureRequest?: CreatePayloadSignatureRequest, + options?: AxiosRequestConfig, + ): AxiosPromise; + + /** + * Get payload signature by the specified payload signature ID. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The onchain address of the address to sign the payload with. + * @param payloadSignatureId - The ID of the payload signature to fetch. + * @param options - Axios request options. + * @throws {APIError} If the request fails. + */ + getPayloadSignature( + walletId: string, + addressid: string, + payloadSignatureId: string, + options?: AxiosRequestConfig, + ): AxiosPromise; + + /** + * List payload signatures for the specified address. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The onchain address of the address to sign the payload with. + * @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param options - Axios request options. + * @throws {APIError} If the request fails. + */ + listPayloadSignatures( + walletId: string, + addressid: string, + limit?: number, + page?: string, + options?: AxiosRequestConfig, + ): AxiosPromise; }; /** @@ -364,6 +425,25 @@ export type ExternalAddressAPIClient = { options?: RawAxiosRequestConfig, ): AxiosPromise; + /** + * List the transactions of a specific address. + * + * @summary Get address transactions + * @param networkId - The ID of the blockchain network + * @param addressId - The ID of the address to fetch transactions for. + * @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param options - Override http request option. + * @throws {RequiredError} + */ + listAddressTransactions( + networkId: string, + addressId: string, + limit?: number, + page?: string, + options?: RawAxiosRequestConfig, + ): AxiosPromise; + /** * Request faucet funds to be sent to external address. * @@ -678,6 +758,7 @@ export type ApiClients = { externalAddress?: ExternalAddressAPIClient; webhook?: WebhookApiClient; smartContract?: ExternalSmartContractAPIClient; + contractInvocation?: ContractInvocationAPIClient; }; /** @@ -731,6 +812,15 @@ export enum ValidatorStatus { REAPED = "reaped", } +/** + * Payload Signature status type definition. + */ +export enum PayloadSignatureStatus { + PENDING = "pending", + SIGNED = "signed", + FAILED = "failed", +} + /** * The Wallet Data type definition. * The data required to recreate a Wallet. @@ -863,8 +953,6 @@ export type CreateTransferOptions = { amount: Amount; assetId: string; destination: Destination; - timeoutSeconds?: number; - intervalSeconds?: number; gasless?: boolean; }; @@ -875,8 +963,16 @@ export type CreateTradeOptions = { amount: Amount; fromAssetId: string; toAssetId: string; - timeoutSeconds?: number; - intervalSeconds?: number; +}; + +/** + * Options for creating a Contract Invocation. + */ +export type CreateContractInvocationOptions = { + contractAddress: string; + abi?: object; + method: string; + args: object; }; /** @@ -888,6 +984,22 @@ export type ListHistoricalBalancesOptions = { page?: string; }; +/** + * Options for listing transactions of an address. + */ +export type ListTransactionsOptions = { + limit?: number; + page?: string; +}; + +/** + * Result of ListTransactions. + */ +export type ListTransactionsResult = { + transactions: Transaction[]; + nextPageToken: string; +}; + /** * Result of ListHistoricalBalances. */ @@ -950,3 +1062,136 @@ export interface WebhookApiClient { options?: RawAxiosRequestConfig, ): AxiosPromise; } + +/** + * The domain for an EIP-712 typed data message payload. + */ +export type TypedDataDomain = { + /** + * The human-readable name of the signing domain. + */ + name?: string; + + /** + * The major version of the signing domain. + */ + version?: string; + + /** + * The chain ID of the signing domain. + */ + chainId?: number; + + /** + * The the address of the contract that will verify the signature. + */ + verifyingContract?: string; + + /** + * A salt used for purposes decided by the specific domain as a data hex string. + */ + salt?: string; +}; + +/** + * A specific field of a structured EIP-712 type. + */ +export type TypedDataField = { + /** + * The field name. + */ + name: string; + + /** + * The type of the field. + */ + type: string; +}; + +/** + * Options for creating a Webhook. + */ +export type CreateWebhookOptions = { + networkId: string; + notificationUri: string; + eventType: WebhookEventType; + eventFilters?: Array; + signatureHeader?: string; +}; + +/** + * ContractInvocationAPI client type definition. + */ +export type ContractInvocationAPIClient = { + /** + * Broadcasts a contract invocation. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The ID of the address the contract invocation belongs to. + * @param contractInvocationId - The ID of the contract invocation to broadcast. + * @param broadcastContractInvocationRequest - The request body. + * @param options - Axios request options. + * @returns - A promise resolving to the ContractInvocation model. + * @throws {APIError} If the request fails. + */ + broadcastContractInvocation( + walletId: string, + addressId: string, + contractInvocationId: string, + broadcastContractInvocationRequest: BroadcastContractInvocationRequest, + options?: AxiosRequestConfig, + ): AxiosPromise; + + /** + * Creates a Contract Invocation. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The ID of the address the contract invocation belongs to. + * @param createContractInvocationRequest - The request body. + * @param options - Axios request options. + * @returns - A promise resolving to the ContractInvocation model. + * @throws {APIError} If the request fails. + */ + createContractInvocation( + walletId: string, + addressId: string, + createContractInvocationRequest: CreateContractInvocationRequest, + options?: AxiosRequestConfig, + ): AxiosPromise; + + /** + * Retrieves a Contract Invocation. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The ID of the address the contract invocation belongs to. + * @param contractInvocationId - The ID of the contract invocation to retrieve. + * @param options - Axios request options. + * @returns - A promise resolving to the ContractInvocation model. + * @throws {APIError} If the request fails. + */ + getContractInvocation( + walletId: string, + addressId: string, + contractInvocationId: string, + options?: AxiosRequestConfig, + ): AxiosPromise; + + /** + * Lists Contract Invocations. + * + * @param walletId - The ID of the wallet the address belongs to. + * @param addressId - The ID of the address the contract invocations belong to. + * @param limit - The maximum number of contract invocations to return. + * @param page - The cursor for pagination across multiple pages of contract invocations. + * @param options - Axios request options. + * @returns - A promise resolving to the ContractInvocation list. + * @throws {APIError} If the request fails. + */ + listContractInvocations( + walletId: string, + addressId: string, + limit?: number, + page?: string, + options?: AxiosRequestConfig, + ): AxiosPromise; +}; diff --git a/src/coinbase/wallet.ts b/src/coinbase/wallet.ts index 1fc53e41..5883399a 100644 --- a/src/coinbase/wallet.ts +++ b/src/coinbase/wallet.ts @@ -17,6 +17,7 @@ import { Trade } from "./trade"; import { Transfer } from "./transfer"; import { Amount, + CreateContractInvocationOptions, CreateTransferOptions, CreateTradeOptions, ListHistoricalBalancesOptions, @@ -31,6 +32,8 @@ import { convertStringToHex, delay, formatDate, getWeekBackDate } from "./utils" import { StakingOperation } from "./staking_operation"; import { StakingReward } from "./staking_reward"; import { StakingBalance } from "./staking_balance"; +import { PayloadSignature } from "./payload_signature"; +import { ContractInvocation } from "../coinbase/contract_invocation"; /** * A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses, @@ -755,6 +758,44 @@ export class Wallet { return await this.getDefaultAddress()!.createTransfer(options); } + /** + * Creates a Payload Signature. + * + * @param unsignedPayload - The Unsigned Payload to sign. + * @returns A promise that resolves to the Payload Signature object. + * @throws {APIError} if the API request to create a Payload Signature fails. + * @throws {Error} if the default address is not found. + */ + public async createPayloadSignature(unsignedPayload: string): Promise { + if (!this.getDefaultAddress()) { + throw new Error("Default address not found"); + } + + return await this.getDefaultAddress()!.createPayloadSignature(unsignedPayload); + } + + /** + * Invokes a contract with the given data. + * + * @param options - The options to invoke the contract + * @param options.contractAddress - The address of the contract the method will be invoked on. + * @param options.method - The method to invoke on the contract. + * @param options.abi - The ABI of the contract. + * @param options.args - The arguments to pass to the contract method invocation. + * The keys should be the argument names and the values should be the argument values. + * @returns The ContractInvocation object. + * @throws {APIError} if the API request to create a contract invocation fails. + */ + public async invokeContract( + options: CreateContractInvocationOptions, + ): Promise { + if (!this.getDefaultAddress()) { + throw new Error("Default address not found"); + } + + return await this.getDefaultAddress()!.invokeContract(options); + } + /** * Returns a String representation of the Wallet. * diff --git a/src/coinbase/webhook.ts b/src/coinbase/webhook.ts index 429a0b28..d6cb2638 100644 --- a/src/coinbase/webhook.ts +++ b/src/coinbase/webhook.ts @@ -1,5 +1,6 @@ import { Webhook as WebhookModel, WebhookEventType, WebhookEventFilter } from "../client/api"; import { Coinbase } from "./coinbase"; +import { CreateWebhookOptions } from "./types"; /** * A representation of a Webhook, @@ -35,23 +36,28 @@ export class Webhook { /** * Creates a new webhook for a specified network. * - * @param networkId - The network ID for which the webhook is created. - * @param notificationUri - The URI where notifications should be sent. - * @param eventType - The type of event for the webhook. - * @param eventFilters - Filters applied to the events that determine which specific events trigger the webhook. + * @param options - The options to create webhook. + * @param options.networkId - The network ID for which the webhook is created. + * @param options.notificationUri - The URI where notifications should be sent. + * @param options.eventType - The type of event for the webhook. + * @param options.eventFilters - Filters applied to the events that determine which specific events trigger the webhook. + * @param options.signatureHeader - The custom header to be used for x-webhook-signature header on callbacks, + * so developers can verify the requests are coming from Coinbase. * @returns A promise that resolves to a new instance of Webhook. */ - public static async create( - networkId: string, - notificationUri: string, - eventType: WebhookEventType, - eventFilters: Array, - ): Promise { + public static async create({ + networkId, + notificationUri, + eventType, + eventFilters = [], + signatureHeader = "", + }: CreateWebhookOptions): Promise { const result = await Coinbase.apiClients.webhook!.createWebhook({ network_id: networkId, notification_uri: notificationUri, event_type: eventType, event_filters: eventFilters, + signature_header: signatureHeader, }); return new Webhook(result.data); @@ -134,6 +140,15 @@ export class Webhook { return this.model?.event_filters; } + /** + * Returns the signature header of the webhook. + * + * @returns The signature header which will be set on the callback requests, or undefined if the model is null. + */ + public getSignatureHeader(): string | undefined { + return this.model?.signature_header; + } + /** * Updates the webhook with a new notification URI. * @@ -169,9 +184,9 @@ export class Webhook { */ public toString(): string { return ( - `Webhook { id: '${this.getId()}', network_id: '${this.getNetworkId()}', ` + - `event_type: '${this.getEventType()}', event_filter: '${JSON.stringify(this.getEventFilters())} ` + - `notification_uri: '${this.getNotificationURI()}' }` + `Webhook { id: '${this.getId()}', networkId: '${this.getNetworkId()}', ` + + `eventType: '${this.getEventType()}', eventFilter: ${JSON.stringify(this.getEventFilters())}, ` + + `notificationUri: '${this.getNotificationURI()}', signatureHeader: '${this.getSignatureHeader()}' }` ); } } diff --git a/src/index.ts b/src/index.ts index 1c6602f4..485f509e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,3 +20,5 @@ export * from "./coinbase/staking_balance"; export * from "./coinbase/validator"; export * from "./coinbase/webhook"; export * from "./coinbase/smart_contract"; +export * from "./coinbase/payload_signature"; +export * from "./coinbase/hash"; diff --git a/src/tests/address_test.ts b/src/tests/address_test.ts index 957690af..3ef285c4 100644 --- a/src/tests/address_test.ts +++ b/src/tests/address_test.ts @@ -1,6 +1,6 @@ import { Coinbase } from "../coinbase/coinbase"; -import { Address } from "../index"; -import { AddressHistoricalBalanceList } from "../client"; +import { Address, TransactionStatus } from "../index"; +import { AddressHistoricalBalanceList, AddressTransactionList } from "../client"; import { VALID_ADDRESS_MODEL, mockReturnValue, @@ -36,6 +36,96 @@ describe("Address", () => { }); }); + describe("#listTransactions", () => { + beforeEach(() => { + const mockTransactionsResponse: AddressTransactionList = { + data: [ + { + network_id: "base-sepolia", + from_address_id: "from_address", + block_hash: "0x0dadd465fb063ceb78babbb30abbc6bfc0730d0c57a53e8f6dc778dafcea568f", + block_height: "12345", + unsigned_payload: "", + status: TransactionStatus.COMPLETE, + }, + { + network_id: "base-sepolia", + from_address_id: "from_address_1", + block_hash: "block_hash", + block_height: "12348", + unsigned_payload: "", + status: TransactionStatus.FAILED, + }, + ], + has_more: true, + next_page: "pageToken", + }; + Coinbase.apiClients.externalAddress = externalAddressApiMock; + Coinbase.apiClients.externalAddress!.listAddressTransactions = + mockReturnValue(mockTransactionsResponse); + }); + + it("should return results with param", async () => { + const result = await address.listTransactions({ limit: 2, page: "page" }); + expect(result.transactions.length).toEqual(2); + expect(result.transactions[0].blockHeight()).toEqual("12345"); + expect(Coinbase.apiClients.externalAddress!.listAddressTransactions).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.externalAddress!.listAddressTransactions).toHaveBeenCalledWith( + address.getNetworkId(), + address.getId(), + 2, + "page", + ); + expect(result.nextPageToken).toEqual("pageToken"); + }); + + it("should return results without param", async () => { + Coinbase.apiClients.externalAddress!.listAddressTransactions = mockReturnValue({ + data: [ + { + network_id: "base-sepolia", + from_address_id: "from_address_1", + block_hash: "block_hash", + block_height: "12348", + unsigned_payload: "", + status: TransactionStatus.COMPLETE, + }, + ], + has_more: false, + next_page: "", + }); + const result = await address.listTransactions({}); + expect(result.transactions.length).toEqual(1); + expect(result.transactions[0].blockHeight()).toEqual("12348"); + expect(Coinbase.apiClients.externalAddress!.listAddressTransactions).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.externalAddress!.listAddressTransactions).toHaveBeenCalledWith( + address.getNetworkId(), + address.getId(), + undefined, + undefined, + ); + expect(result.nextPageToken).toEqual(""); + }); + + it("should return empty if no transactions found", async () => { + Coinbase.apiClients.externalAddress!.listAddressTransactions = mockReturnValue({ + data: [], + has_more: false, + next_page: "", + }); + const result = await address.listTransactions({}); + expect(result.transactions.length).toEqual(0); + expect(Coinbase.apiClients.externalAddress!.listAddressTransactions).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.externalAddress!.listAddressTransactions).toHaveBeenCalledWith( + address.getNetworkId(), + address.getId(), + undefined, + undefined, + ); + expect(result.nextPageToken).toEqual(""); + }); + }); + describe(".listHistoricalBalance", () => { beforeEach(() => { const mockHistoricalBalanceResponse: AddressHistoricalBalanceList = { diff --git a/src/tests/contract_invocation_test.ts b/src/tests/contract_invocation_test.ts new file mode 100644 index 00000000..27f70d6a --- /dev/null +++ b/src/tests/contract_invocation_test.ts @@ -0,0 +1,391 @@ +import { ethers } from "ethers"; +import { AxiosError } from "axios"; +import { Decimal } from "decimal.js"; +import { + ContractInvocation as ContractInvocationModel, + TransactionStatusEnum, +} from "../client/api"; +import { TransactionStatus } from "../coinbase/types"; +import { ContractInvocation } from "../coinbase/contract_invocation"; +import { Transaction } from "../coinbase/transaction"; +import { Coinbase } from "../coinbase/coinbase"; +import { + VALID_CONTRACT_INVOCATION_MODEL, + VALID_SIGNED_CONTRACT_INVOCATION_MODEL, + mockReturnValue, + mockReturnRejectedValue, + contractInvocationApiMock, + MINT_NFT_ARGS, + MINT_NFT_ABI, +} from "./utils"; + +import { TimeoutError } from "../coinbase/errors"; +import { APIError } from "../coinbase/api_error"; + +describe("Contract Invocation Class", () => { + let contractInvocationModel: ContractInvocationModel; + let contractInvocation: ContractInvocation; + + beforeEach(() => { + Coinbase.apiClients.contractInvocation = contractInvocationApiMock; + + contractInvocationModel = VALID_CONTRACT_INVOCATION_MODEL; + contractInvocation = ContractInvocation.fromModel(contractInvocationModel); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe("constructor", () => { + it("initializes a new ContractInvocation", () => { + expect(contractInvocation).toBeInstanceOf(ContractInvocation); + }); + + it("raises an error when the contractInvocation model is empty", () => { + expect(() => ContractInvocation.fromModel(undefined!)).toThrow( + "ContractInvocation model cannot be empty", + ); + }); + }); + + describe("#getId", () => { + it("returns the contract invocation ID", () => { + expect(contractInvocation.getId()).toEqual( + VALID_CONTRACT_INVOCATION_MODEL.contract_invocation_id, + ); + }); + }); + + describe("#getNetworkId", () => { + it("returns the network ID", () => { + expect(contractInvocation.getNetworkId()).toEqual(VALID_CONTRACT_INVOCATION_MODEL.network_id); + }); + }); + + describe("#getWalletId", () => { + it("returns the wallet ID", () => { + expect(contractInvocation.getWalletId()).toEqual(VALID_CONTRACT_INVOCATION_MODEL.wallet_id); + }); + }); + + describe("#getFromAddressId", () => { + it("returns the source address ID", () => { + expect(contractInvocation.getFromAddressId()).toEqual( + VALID_CONTRACT_INVOCATION_MODEL.address_id, + ); + }); + }); + + describe("#getContractAddressId", () => { + it("returns the contract address ID", () => { + expect(contractInvocation.getContractAddressId()).toEqual( + VALID_CONTRACT_INVOCATION_MODEL.contract_address, + ); + }); + }); + + describe("#getMethod", () => { + it("return the conrtact invocation's method", () => { + expect(contractInvocation.getMethod()).toEqual(VALID_CONTRACT_INVOCATION_MODEL.method); + }); + }); + + describe("#getArgs", () => { + it("returns the parsed arguments", () => { + expect(contractInvocation.getArgs()).toEqual(MINT_NFT_ARGS); + }); + }); + + describe("#getAbi", () => { + it("returns the parsed ABI", () => { + expect(contractInvocation.getAbi()).toEqual(MINT_NFT_ABI); + }); + }); + + describe("#getTransactionHash", () => { + describe("when the transaction has a hash", () => { + let transactionHash = "0xtransactionHash"; + + beforeEach(() => { + contractInvocation = ContractInvocation.fromModel({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + transaction_hash: transactionHash, + }, + }); + }); + + it("returns the transaction hash", () => { + expect(contractInvocation.getTransactionHash()).toEqual(transactionHash); + }); + }); + + describe("when the transaction does not have a hash", () => { + it("returns undefined", () => { + expect(contractInvocation.getTransactionHash()).toBeUndefined(); + }); + }); + }); + + describe("#getTransactionLink", () => { + describe("when the transaction has a transaction link", () => { + let transactionLink = `https://sepolia.basescan.org/tx/0xtransactionHash`; + + beforeEach(() => { + contractInvocation = ContractInvocation.fromModel({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + transaction_link: transactionLink, + }, + }); + }); + + it("returns the transaction link", () => { + expect(contractInvocation.getTransactionLink()).toEqual(transactionLink); + }); + }); + + describe("when the transaction does not have a link", () => { + it("returns undefined", () => { + expect(contractInvocation.getTransactionLink()).toBeUndefined(); + }); + }); + }); + + describe("#getTransaction", () => { + it("returns the transaction", () => { + expect(contractInvocation.getTransaction()).toBeInstanceOf(Transaction); + }); + }); + + describe("#getRawTransaction", () => { + it("returns the ContractInvocation raw transaction", () => { + expect(contractInvocation.getRawTransaction()).toBeInstanceOf(ethers.Transaction); + }); + }); + + describe("#getStatus", () => { + let txStatus; + + beforeEach(() => { + contractInvocationModel = { + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + status: txStatus, + }, + }; + + contractInvocation = ContractInvocation.fromModel(contractInvocationModel); + }); + + [ + TransactionStatus.PENDING, + TransactionStatus.BROADCAST, + TransactionStatus.COMPLETE, + TransactionStatus.FAILED, + ].forEach(status => { + describe(`when the transaction has status ${status}`, () => { + beforeAll(() => (txStatus = status)); + afterAll(() => (txStatus = undefined)); + + it("returns the correct status", async () => { + expect(contractInvocation.getStatus()).toEqual(status); + }); + }); + }); + }); + + describe("#broadcast", () => { + let signedPayload = "0xsignedHash"; + + beforeEach(() => { + // Ensure signed payload is present. + contractInvocation = ContractInvocation.fromModel({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + signed_payload: signedPayload, + }, + }); + }); + + describe("when it was successful", () => { + let broadcastedInvocation; + + beforeEach(async () => { + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + signed_payload: signedPayload, + status: TransactionStatus.BROADCAST, + }, + }); + + broadcastedInvocation = await contractInvocation.broadcast(); + }); + + it("returns the broadcasted contract invocation", async () => { + expect(broadcastedInvocation).toBeInstanceOf(ContractInvocation); + expect(broadcastedInvocation.getStatus()).toEqual(TransactionStatus.BROADCAST); + }); + + it("broadcasts the contract invocation", async () => { + expect( + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation, + ).toHaveBeenCalledWith( + contractInvocation.getWalletId(), + contractInvocation.getFromAddressId(), + contractInvocation.getId(), + { + signed_payload: signedPayload.slice(2), + }, + ); + + expect( + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe("when the transaction is not signed", () => { + beforeEach(() => { + contractInvocation = ContractInvocation.fromModel(VALID_CONTRACT_INVOCATION_MODEL); + }); + + it("throws an error", async () => { + expect(contractInvocation.broadcast()).rejects.toThrow( + "Cannot broadcast unsigned ContractInvocation", + ); + }); + }); + + describe("when broadcasting fails", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation = + mockReturnRejectedValue( + new APIError({ + response: { + status: 400, + data: { + code: "invalid_signed_payload", + message: "failed to broadcast contract invocation: invalid signed payload", + }, + }, + } as AxiosError), + ); + }); + + it("throws an error", async () => { + expect(contractInvocation.broadcast()).rejects.toThrow(APIError); + }); + }); + }); + + describe("#sign", () => { + let signingKey: any = ethers.Wallet.createRandom(); + + it("return the signature", async () => { + const contractInvocation = ContractInvocation.fromModel({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + signed_payload: "0xsignedHash", + }, + }); + + const signature = await contractInvocation.sign(signingKey); + + expect(signature).toEqual(contractInvocation.getTransaction()!.getSignature()!); + }); + }); + + describe("#wait", () => { + describe("when the transaction is complete", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.getContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + status: TransactionStatusEnum.Complete, + }, + }); + }); + + it("successfully waits and returns", async () => { + const completedContractInvocation = await contractInvocation.wait(); + expect(completedContractInvocation).toBeInstanceOf(ContractInvocation); + expect(completedContractInvocation.getStatus()).toEqual(TransactionStatus.COMPLETE); + }); + }); + + describe("when the transaction is failed", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.getContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + status: TransactionStatusEnum.Failed, + }, + status: TransactionStatus.FAILED, + }); + }); + + it("successfully waits and returns a failed invocation", async () => { + const completedContractInvocation = await contractInvocation.wait(); + expect(completedContractInvocation).toBeInstanceOf(ContractInvocation); + expect(completedContractInvocation.getStatus()).toEqual(TransactionStatus.FAILED); + }); + }); + + describe("when the transaction is pending", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.getContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + status: TransactionStatusEnum.Pending, + }, + }); + }); + + it("throws a timeout error", async () => { + expect( + contractInvocation.wait({ timeoutSeconds: 0.05, intervalSeconds: 0.05 }), + ).rejects.toThrow(new TimeoutError("ContractInvocation timed out")); + }); + }); + }); + + describe("#reload", () => { + it("returns the updated contract invocation", async () => { + Coinbase.apiClients.contractInvocation!.getContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction!, + status: TransactionStatusEnum.Complete, + }, + }); + await contractInvocation.reload(); + expect(contractInvocation.getStatus()).toEqual(TransactionStatus.COMPLETE); + expect(Coinbase.apiClients.contractInvocation!.getContractInvocation).toHaveBeenCalledTimes( + 1, + ); + }); + }); + + describe("#toString", () => { + it("returns the same value as toString", () => { + expect(contractInvocation.toString()).toEqual( + `ContractInvocation{contractInvocationId: '${contractInvocation.getId()}', networkId: '${contractInvocation.getNetworkId()}', ` + + `fromAddressId: '${contractInvocation.getFromAddressId()}', contractAddressId: '${contractInvocation.getContractAddressId()}', ` + + `method: '${contractInvocation.getMethod()}', args: '${contractInvocation.getArgs()}', transactionHash: '${contractInvocation.getTransactionHash()}', ` + + `transactionLink: '${contractInvocation.getTransactionLink()}', status: '${contractInvocation.getStatus()!}'}`, + ); + }); + }); +}); diff --git a/src/tests/e2e.ts b/src/tests/e2e.ts index 54fc641c..f2f8bd62 100644 --- a/src/tests/e2e.ts +++ b/src/tests/e2e.ts @@ -104,6 +104,20 @@ describe("Coinbase SDK E2E Test", () => { console.log(`First address balances: ${firstBalance}`); console.log(`Second address balances: ${secondBalance}`); + console.log("Fetching address transactions..."); + const result = await unhydratedWallet.getDefaultAddress()?.listTransactions({ limit: 1 }); + expect(result?.transactions.length).toBeGreaterThan(0); + console.log(`Fetched transactions: ${result?.transactions[0].toString()}`); + + console.log("Fetching address historical balances..."); + const balance_result = await unhydratedWallet + .getDefaultAddress() + ?.listHistoricalBalances({ assetId: Coinbase.assets.Eth, limit: 2 }); + expect(balance_result?.historicalBalances.length).toBeGreaterThan(0); + console.log( + `First eth historical balance: ${balance_result?.historicalBalances[0].amount.toString()}`, + ); + const savedSeed = JSON.parse(fs.readFileSync("test_seed.json", "utf-8")); fs.unlinkSync("test_seed.json"); diff --git a/src/tests/hash_test.ts b/src/tests/hash_test.ts new file mode 100644 index 00000000..95828c9e --- /dev/null +++ b/src/tests/hash_test.ts @@ -0,0 +1,106 @@ +import { ethers } from "ethers"; +import { hashMessage, hashTypedDataMessage } from "../coinbase/hash"; + +describe("hashMessage", () => { + const mockHashMessage = jest.spyOn(ethers, "hashMessage"); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should hash a string message correctly using EIP-191", () => { + const message = "Hello, Ethereum!"; + const expectedHash = "0xExpectedHash"; + + mockHashMessage.mockReturnValue(expectedHash); + + const result = hashMessage(message); + expect(result).toBe(expectedHash); + expect(mockHashMessage).toHaveBeenCalledWith(message); + expect(mockHashMessage).toHaveBeenCalledTimes(1); + }); + + it("should throw an error if ethers throws an error", () => { + const invalidMessage = 12345; + const expectedError = new Error("invalid message"); + + mockHashMessage.mockImplementation(() => { + throw expectedError; + }); + + expect(() => hashMessage(invalidMessage as any)).toThrow(expectedError); + expect(mockHashMessage).toHaveBeenCalledWith(invalidMessage); + expect(mockHashMessage).toHaveBeenCalledTimes(1); + }); +}); + +describe("hashTypedDataMessage", () => { + const mockTypedDataEncoderHash = jest.spyOn(ethers.TypedDataEncoder, "hash"); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should hash typed data message correctly using EIP-712", () => { + const domain = { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }; + + const types = { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + }; + + const value = { + name: "Alice", + wallet: "0x123456789abcdef123456789abcdef123456789a", + }; + + const expectedHash = "0xExpectedHash"; + + mockTypedDataEncoderHash.mockReturnValue(expectedHash); + + const result = hashTypedDataMessage(domain, types, value); + expect(result).toBe(expectedHash); + expect(mockTypedDataEncoderHash).toHaveBeenCalledWith(domain, types, value); + expect(mockTypedDataEncoderHash).toHaveBeenCalledTimes(1); + }); + + it("should throw an error if ethers throws an error", () => { + const domain = { + name: "Invalid", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }; + + const types = { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + }; + + const value = { + name: "InvalidName", + wallet: "invalidWallet", + }; + + const expectedError = new Error("invalid typed data message"); + + mockTypedDataEncoderHash.mockImplementation(() => { + throw expectedError; + }); + + expect(() => { + hashTypedDataMessage(domain, types, value); + }).toThrow(expectedError); + expect(mockTypedDataEncoderHash).toHaveBeenCalledWith(domain, types, value); + expect(mockTypedDataEncoderHash).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/tests/payload_signature_test.ts b/src/tests/payload_signature_test.ts new file mode 100644 index 00000000..c769d514 --- /dev/null +++ b/src/tests/payload_signature_test.ts @@ -0,0 +1,214 @@ +import { PayloadSignature } from "../coinbase/payload_signature"; +import { + VALID_PAYLOAD_SIGNATURE_MODEL, + VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL, + addressesApiMock, + mockReturnValue, + mockReturnRejectedValue, +} from "./utils"; +import { PayloadSignatureStatusEnum } from "../client"; +import { Coinbase } from "../coinbase/coinbase"; +import { APIError } from "../coinbase/api_error"; + +describe("PayloadSignature", () => { + beforeEach(() => {}); + + describe("constructor", () => { + it("initializes a new PayloadSignature", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature).toBeInstanceOf(PayloadSignature); + }); + + it("should raise an error when initialized with an invalid model", () => { + expect(() => new PayloadSignature(null!)).toThrow("Invalid model type"); + }); + }); + + describe("#getId", () => { + it("should return the Payload Signature ID", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getId()).toEqual(VALID_PAYLOAD_SIGNATURE_MODEL.payload_signature_id); + }); + }); + + describe("#getWalletId", () => { + it("should return the Wallet ID", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getWalletId()).toEqual(VALID_PAYLOAD_SIGNATURE_MODEL.wallet_id); + }); + }); + + describe("#getAddressId", () => { + it("should return the Address ID", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getAddressId()).toEqual(VALID_PAYLOAD_SIGNATURE_MODEL.address_id); + }); + }); + + describe("#getUnsignedPayload", () => { + it("should return the Unsigned Payload", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getUnsignedPayload()).toEqual( + VALID_PAYLOAD_SIGNATURE_MODEL.unsigned_payload, + ); + }); + }); + + describe("#getSignature", () => { + it("should return undefined when the PayloadSignature has not been signed", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getSignature()).toBeUndefined(); + }); + + it("should return the signature when the PayloadSignature has been signed", () => { + const payloadSignature = new PayloadSignature(VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getSignature()).toEqual( + VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL.signature, + ); + }); + }); + + describe("#getStatus", () => { + it("should return a pending status", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getStatus()).toEqual("pending"); + }); + + it("should return a signed status", () => { + const payloadSignature = new PayloadSignature(VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.getStatus()).toEqual("signed"); + }); + + it("should return a failed status", () => { + const payloadSignature = new PayloadSignature({ + ...VALID_PAYLOAD_SIGNATURE_MODEL, + status: PayloadSignatureStatusEnum.Failed, + }); + expect(payloadSignature.getStatus()).toEqual("failed"); + }); + }); + + describe("#isTerminalState", () => { + it("should not be in a terminal state", () => { + const payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.isTerminalState()).toEqual(false); + }); + + it("should be in a terminal state", () => { + const payloadSignature = new PayloadSignature(VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.isTerminalState()).toEqual(true); + }); + }); + + describe("#wait", () => { + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should update Payload Signature model", async () => { + Coinbase.apiClients.address!.getPayloadSignature = mockReturnValue( + VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL, + ); + + let payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + await payloadSignature.wait(); + + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledWith( + VALID_PAYLOAD_SIGNATURE_MODEL.wallet_id, + VALID_PAYLOAD_SIGNATURE_MODEL.address_id, + VALID_PAYLOAD_SIGNATURE_MODEL.payload_signature_id, + ); + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledTimes(1); + expect(payloadSignature.getStatus()).toEqual("signed"); + expect(payloadSignature.isTerminalState()).toEqual(true); + }); + + it("should throw an APIError when the API call to get payload signature fails", async () => { + Coinbase.apiClients.address!.getPayloadSignature = mockReturnRejectedValue( + new APIError("Failed to get payload signature"), + ); + + let payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + + expect(async () => { + await payloadSignature.reload(); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledWith( + VALID_PAYLOAD_SIGNATURE_MODEL.wallet_id, + VALID_PAYLOAD_SIGNATURE_MODEL.address_id, + VALID_PAYLOAD_SIGNATURE_MODEL.payload_signature_id, + ); + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledTimes(1); + }); + }); + + describe("#reload", () => { + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should update Payload Signature model", async () => { + Coinbase.apiClients.address!.getPayloadSignature = mockReturnValue( + VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL, + ); + + let payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + await payloadSignature.reload(); + + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledWith( + VALID_PAYLOAD_SIGNATURE_MODEL.wallet_id, + VALID_PAYLOAD_SIGNATURE_MODEL.address_id, + VALID_PAYLOAD_SIGNATURE_MODEL.payload_signature_id, + ); + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledTimes(1); + expect(payloadSignature.getStatus()).toEqual("signed"); + }); + + it("should throw an APIError when the API call to get payload signature fails", async () => { + Coinbase.apiClients.address!.getPayloadSignature = mockReturnRejectedValue( + new APIError("Failed to get payload signature"), + ); + + let payloadSignature = new PayloadSignature(VALID_PAYLOAD_SIGNATURE_MODEL); + + expect(async () => { + await payloadSignature.reload(); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledWith( + VALID_PAYLOAD_SIGNATURE_MODEL.wallet_id, + VALID_PAYLOAD_SIGNATURE_MODEL.address_id, + VALID_PAYLOAD_SIGNATURE_MODEL.payload_signature_id, + ); + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledTimes(1); + }); + }); + + describe("#toString", () => { + let payloadSignature: PayloadSignature; + + beforeAll(() => { + payloadSignature = new PayloadSignature(VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL); + }); + + it("includes PayloadSignature details", () => { + expect(payloadSignature.toString()).toContain(payloadSignature.getStatus()); + }); + + it("returns the same value as toString", () => { + const payloadSignature = new PayloadSignature(VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL); + expect(payloadSignature.toString()).toEqual( + `PayloadSignature { status: '${payloadSignature.getStatus()}', unsignedPayload: '${payloadSignature.getUnsignedPayload()}', signature: ${payloadSignature.getSignature()} }`, + ); + }); + }); +}); diff --git a/src/tests/transaction_test.ts b/src/tests/transaction_test.ts index 6233364c..95750904 100644 --- a/src/tests/transaction_test.ts +++ b/src/tests/transaction_test.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { Transaction as TransactionModel } from "../client/api"; +import { Transaction as TransactionModel, EthereumTransaction } from "../client/api"; import { Transaction } from "./../coinbase/transaction"; import { TransactionStatus } from "../coinbase/types"; @@ -13,6 +13,10 @@ describe("Transaction", () => { let model; let broadcastedModel; let transaction; + let ethereumContent; + let onchainModel; + let blockHash; + let blockHeight; beforeEach(() => { fromKey = ethers.Wallet.createRandom(); @@ -34,6 +38,12 @@ describe("Transaction", () => { transactionHash = "0x6c087c1676e8269dd81e0777244584d0cbfd39b6997b3477242a008fa9349e11"; + blockHash = "0x0728750d458976fd010a2e15cef69ec71c6fccb3377f38a71b70ab551ab22688"; + blockHeight = "18779006"; + ethereumContent = { + priority_fee_per_gas: 1000, + } as EthereumTransaction; + model = { status: "pending", from_address_id: fromAddressId, @@ -49,6 +59,15 @@ describe("Transaction", () => { transaction_link: `https://sepolia.basescan.org/tx/${transactionHash}`, } as TransactionModel; + onchainModel = { + status: "complete", + from_address_id: fromAddressId, + unsigned_payload: "", + block_hash: blockHash, + block_height: blockHeight, + content: ethereumContent, + } as TransactionModel; + transaction = new Transaction(model); }); @@ -208,6 +227,39 @@ describe("Transaction", () => { }); }); + describe("#blockHash", () => { + it("returns the block hash", () => { + const transaction = new Transaction(onchainModel); + expect(transaction.blockHash()).toEqual(blockHash); + }); + + it("returns undefined when block hash is undefined", () => { + expect(transaction.blockHash()).toBeUndefined; + }); + }); + + describe("#blockHeight", () => { + it("returns the block height", () => { + const transaction = new Transaction(onchainModel); + expect(transaction.blockHeight()).toEqual(blockHeight); + }); + + it("returns undefined when block height is undefined", () => { + expect(transaction.blockHeight()).toBeUndefined; + }); + }); + + describe("#content", () => { + it("returns the ethereum transaction", () => { + const transaction = new Transaction(onchainModel); + expect(transaction.content()).toEqual(ethereumContent); + }); + + it("returns undefined when content is undefined", () => { + expect(transaction.content()).toBeUndefined; + }); + }); + describe("#fromAddressId", () => { it("should return the from address ID", () => { expect(transaction.fromAddressId()).toEqual(fromAddressId); diff --git a/src/tests/utils.ts b/src/tests/utils.ts index 700d913d..9774515e 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -11,6 +11,10 @@ import { Address as AddressModel, Transfer as TransferModel, StakingOperation as StakingOperationModel, + PayloadSignature as PayloadSignatureModel, + PayloadSignatureList, + PayloadSignatureStatusEnum, + ContractInvocation as ContractInvocationModel, ValidatorList, Validator, StakingOperationStatusEnum, @@ -201,6 +205,74 @@ export const VALID_STAKING_OPERATION_MODEL: StakingOperationModel = { ], }; +export const VALID_PAYLOAD_SIGNATURE_MODEL: PayloadSignatureModel = { + payload_signature_id: "test-payload-signature-id-1", + wallet_id: walletId, + address_id: ethers.Wallet.createRandom().address, + unsigned_payload: "0x58f51af4cb4775cebe5853f0bf1e984927415e889a3d55ae6d243aeec46ffd10", + status: PayloadSignatureStatusEnum.Pending, +}; + +export const VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL: PayloadSignatureModel = { + ...VALID_PAYLOAD_SIGNATURE_MODEL, + signature: "0x58f51af4cb4775cebe5853f0bf1e984927415e889a3d55ae6d243aeec46ffd10", + status: PayloadSignatureStatusEnum.Signed, +}; + +export const VALID_PAYLOAD_SIGNATURE_LIST: PayloadSignatureList = { + data: [ + VALID_PAYLOAD_SIGNATURE_MODEL, + { ...VALID_PAYLOAD_SIGNATURE_MODEL, payload_signature_id: "test-payload-signature-id-2" }, + { ...VALID_PAYLOAD_SIGNATURE_MODEL, payload_signature_id: "test-payload-signature-id-3" }, + { ...VALID_PAYLOAD_SIGNATURE_MODEL, payload_signature_id: "test-payload-signature-id-4" }, + ], + has_more: false, + next_page: "", + total_count: 4, +}; + +export const MINT_NFT_ABI = [ + { + inputs: [{ internalType: "address", name: "recipient", type: "address" }], + name: "mint", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, +]; + +export const MINT_NFT_ARGS = { recipient: "0x475d41de7A81298Ba263184996800CBcaAD73C0b" }; + +export const VALID_CONTRACT_INVOCATION_MODEL: ContractInvocationModel = { + wallet_id: walletId, + address_id: ethers.Wallet.createRandom().address, + contract_invocation_id: "test-contract-invocation-1", + network_id: Coinbase.networks.BaseSepolia, + contract_address: "0xcontract-address", + method: "mint", + args: JSON.stringify(MINT_NFT_ARGS), + abi: JSON.stringify(MINT_NFT_ABI), + transaction: { + network_id: Coinbase.networks.BaseSepolia, + from_address_id: "0xdeadbeef", + unsigned_payload: + "7b2274797065223a22307832222c22636861696e4964223a2230783134613334222c226e6f6e6365223a22307830222c22746f223a22307861383261623835303466646562326461646161336234663037356539363762626533353036356239222c22676173223a22307865623338222c226761735072696365223a6e756c6c2c226d61785072696f72697479466565506572476173223a2230786634323430222c226d6178466565506572476173223a2230786634333638222c2276616c7565223a22307830222c22696e707574223a223078366136323738343230303030303030303030303030303030303030303030303034373564343164653761383132393862613236333138343939363830306362636161643733633062222c226163636573734c697374223a5b5d2c2276223a22307830222c2272223a22307830222c2273223a22307830222c2279506172697479223a22307830222c2268617368223a22307865333131636632303063643237326639313566656433323165663065376431653965353362393761346166623737336638653935646431343630653665326163227d", + status: TransactionStatusEnum.Pending, + }, +}; + +export const VALID_SIGNED_CONTRACT_INVOCATION_MODEL: ContractInvocationModel = { + ...VALID_CONTRACT_INVOCATION_MODEL, + transaction: { + ...VALID_CONTRACT_INVOCATION_MODEL.transaction, + signed_payload: + "02f88f83014a3480830f4240830f436882eb3894a82ab8504fdeb2dadaa3b4f075e967bbe35065b980a46a627842000000000000000000000000475d41de7a81298ba263184996800cbcaad73c0bc080a00bca053345d88d7cc02c257c5d74f8285bc6408c9020e1b4331779995f355c0ca04a8ec5bee1609d97f3ccba1e0d535441cf61c708e9bc632fe9963b34f97d0462", + status: TransactionStatusEnum.Broadcast, + transaction_hash: "0xdummy-transaction-hash", + transaction_link: "https://sepolia.basescan.org/tx/0xdummy-transaction-hash", + }, +}; + /** * mockStakingOperation returns a mock StakingOperation object with the provided status. * @@ -431,6 +503,9 @@ export const addressesApiMock = { getAddressBalance: jest.fn(), listAddressBalances: jest.fn(), createAddress: jest.fn(), + createPayloadSignature: jest.fn(), + getPayloadSignature: jest.fn(), + listPayloadSignatures: jest.fn(), }; export const tradeApiMock = { @@ -471,6 +546,7 @@ export const externalAddressApiMock = { getExternalAddressBalance: jest.fn(), requestExternalFaucetFunds: jest.fn(), listAddressHistoricalBalance: jest.fn(), + listAddressTransactions: jest.fn(), }; export const serverSignersApiMock = { @@ -480,3 +556,10 @@ export const serverSignersApiMock = { export const smartContractApiMock = { listContractEvents: jest.fn(), }; + +export const contractInvocationApiMock = { + getContractInvocation: jest.fn(), + listContractInvocations: jest.fn(), + createContractInvocation: jest.fn(), + broadcastContractInvocation: jest.fn(), +}; diff --git a/src/tests/wallet_address_test.ts b/src/tests/wallet_address_test.ts index 150bbd43..f089131d 100644 --- a/src/tests/wallet_address_test.ts +++ b/src/tests/wallet_address_test.ts @@ -1,16 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as crypto from "crypto"; +import { AxiosError } from "axios"; import { randomUUID } from "crypto"; import { ethers } from "ethers"; import { FaucetTransaction } from "../coinbase/faucet_transaction"; import { + Address as AddressModel, Balance as BalanceModel, FetchStakingRewards200Response, FetchHistoricalStakingBalances200Response, StakingContext as StakingContextModel, StakingOperation as StakingOperationModel, StakingOperationStatusEnum, - TransactionStatusEnum, StakingRewardFormat, StakingRewardStateEnum, Trade as TradeModel, @@ -24,6 +25,7 @@ import { addressesApiMock, assetsApiMock, externalAddressApiMock, + contractInvocationApiMock, generateRandomHash, getAssetMock, mockFn, @@ -38,10 +40,16 @@ import { VALID_ADDRESS_MODEL, VALID_TRANSFER_MODEL, VALID_WALLET_MODEL, + VALID_PAYLOAD_SIGNATURE_MODEL, + VALID_PAYLOAD_SIGNATURE_LIST, + VALID_CONTRACT_INVOCATION_MODEL, + VALID_SIGNED_CONTRACT_INVOCATION_MODEL, + MINT_NFT_ABI, + MINT_NFT_ARGS, walletsApiMock, } from "./utils"; import { Transfer } from "../coinbase/transfer"; -import { StakeOptionsMode, TransactionStatus, TransferStatus } from "../coinbase/types"; +import { TransactionStatus } from "../coinbase/types"; import { Trade } from "../coinbase/trade"; import { Transaction } from "../coinbase/transaction"; import { WalletAddress } from "../coinbase/address/wallet_address"; @@ -49,6 +57,8 @@ import { Wallet } from "../coinbase/wallet"; import { StakingOperation } from "../coinbase/staking_operation"; import { StakingReward } from "../coinbase/staking_reward"; import { StakingBalance } from "../coinbase/staking_balance"; +import { PayloadSignature } from "../coinbase/payload_signature"; +import { ContractInvocation } from "../coinbase/contract_invocation"; // Test suite for the WalletAddress class describe("WalletAddress", () => { @@ -60,7 +70,6 @@ describe("WalletAddress", () => { beforeEach(() => { Coinbase.apiClients.externalAddress = externalAddressApiMock; Coinbase.apiClients.asset = assetsApiMock; - Coinbase.apiClients.externalAddress = externalAddressApiMock; Coinbase.apiClients.asset.getAsset = getAssetMock(); Coinbase.apiClients.externalAddress.getExternalAddressBalance = mockFn(request => { const [, , asset_id] = request; @@ -258,6 +267,38 @@ describe("WalletAddress", () => { ); }); + describe("#setKey", () => { + it("should set the key successfully", () => { + key = ethers.Wallet.createRandom(); + const newAddress = new WalletAddress(VALID_ADDRESS_MODEL, undefined); + expect(() => { + newAddress.setKey(key); + }).not.toThrow(Error); + }); + it("should not set the key successfully", () => { + key = ethers.Wallet.createRandom(); + const newAddress = new WalletAddress(VALID_ADDRESS_MODEL, key); + expect(() => { + newAddress.setKey(key); + }).toThrow(Error); + }); + }); + + describe("#export", () => { + it("should get the private key if it is set", () => { + key = ethers.Wallet.createRandom(); + const newAddress = new WalletAddress(VALID_ADDRESS_MODEL, key); + expect(newAddress.export()).toEqual(key.privateKey); + }); + + it("should not get the private key if not set", () => { + const newAddress = new WalletAddress(VALID_ADDRESS_MODEL, undefined); + expect(() => { + newAddress.export(); + }).toThrow(Error); + }); + }); + describe("#stakingOperation", () => { key = ethers.Wallet.createRandom(); const newAddress = newAddressModel("", randomUUID(), Coinbase.networks.EthereumHolesky); @@ -424,7 +465,7 @@ describe("WalletAddress", () => { STAKING_OPERATION_MODEL.wallet_id = newAddress.wallet_id; }); - describe(".createStake", () => { + describe("#createStake", () => { it("should create a staking operation from the address", async () => { Coinbase.apiClients.asset!.getAsset = getAssetMock(); Coinbase.apiClients.stake!.getStakingContext = mockReturnValue(STAKING_CONTEXT_MODEL); @@ -485,7 +526,7 @@ describe("WalletAddress", () => { }); }); - describe(".createUnstake", () => { + describe("#createUnstake", () => { it("should create a staking operation from the address", async () => { Coinbase.apiClients.asset!.getAsset = getAssetMock(); Coinbase.apiClients.stake!.getStakingContext = mockReturnValue(STAKING_CONTEXT_MODEL); @@ -503,7 +544,7 @@ describe("WalletAddress", () => { }); }); - describe(".createClaimStake", () => { + describe("#createClaimStake", () => { it("should create a staking operation from the address", async () => { Coinbase.apiClients.asset!.getAsset = getAssetMock(); Coinbase.apiClients.stake!.getStakingContext = mockReturnValue(STAKING_CONTEXT_MODEL); @@ -521,7 +562,7 @@ describe("WalletAddress", () => { }); }); - describe(".stakeableBalance", () => { + describe("#stakeableBalance", () => { it("should return the stakeable balance successfully with default params", async () => { Coinbase.apiClients.stake!.getStakingContext = mockReturnValue(STAKING_CONTEXT_MODEL); const stakeableBalance = await walletAddress.stakeableBalance(Coinbase.assets.Eth); @@ -529,7 +570,7 @@ describe("WalletAddress", () => { }); }); - describe(".unstakeableBalance", () => { + describe("#unstakeableBalance", () => { it("should return the unstakeableBalance balance successfully with default params", async () => { Coinbase.apiClients.stake!.getStakingContext = mockReturnValue(STAKING_CONTEXT_MODEL); const stakeableBalance = await walletAddress.unstakeableBalance(Coinbase.assets.Eth); @@ -537,7 +578,7 @@ describe("WalletAddress", () => { }); }); - describe(".claimableBalance", () => { + describe("#claimableBalance", () => { it("should return the claimableBalance balance successfully with default params", async () => { Coinbase.apiClients.stake!.getStakingContext = mockReturnValue(STAKING_CONTEXT_MODEL); const stakeableBalance = await walletAddress.claimableBalance(Coinbase.assets.Eth); @@ -545,7 +586,7 @@ describe("WalletAddress", () => { }); }); - describe(".stakingRewards", () => { + describe("#stakingRewards", () => { it("should successfully return staking rewards", async () => { Coinbase.apiClients.stake!.fetchStakingRewards = mockReturnValue(STAKING_REWARD_RESPONSE); Coinbase.apiClients.asset!.getAsset = getAssetMock(); @@ -554,7 +595,7 @@ describe("WalletAddress", () => { }); }); - describe(".historicalStakingBalances", () => { + describe("#historicalStakingBalances", () => { it("should successfully return historical staking balances", async () => { Coinbase.apiClients.stake!.fetchHistoricalStakingBalances = mockReturnValue( HISTORICAL_STAKING_BALANCES_RESPONSE, @@ -736,7 +777,7 @@ describe("WalletAddress", () => { }); }); - describe("#getTransfers", () => { + describe("#listTransfers", () => { beforeEach(() => { jest.clearAllMocks(); const pages = ["abc", "def"]; @@ -1069,22 +1110,461 @@ describe("WalletAddress", () => { ).rejects.toThrow(Error); }); }); + }); + + describe("#invokeContract", () => { + let key = ethers.Wallet.createRandom(); + let addressModel: AddressModel; + let walletAddress: WalletAddress; + let unsignedPayload = VALID_CONTRACT_INVOCATION_MODEL.transaction.unsigned_payload; + let expectedSignedPayload: string; + + beforeAll(() => { + Coinbase.apiClients.contractInvocation = contractInvocationApiMock; + }); + + 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_CONTRACT_INVOCATION_MODEL.transaction); + expectedSignedPayload = await tx.sign(key as unknown as ethers.Wallet); + }); + + describe("when it is successful", () => { + let contractInvocation; + + beforeEach(async () => { + Coinbase.apiClients.contractInvocation!.createContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation = mockReturnValue({ + ...VALID_SIGNED_CONTRACT_INVOCATION_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + contractInvocation = await walletAddress.invokeContract({ + abi: MINT_NFT_ABI, + args: MINT_NFT_ARGS, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }); + }); + + it("returns a contract invocation", async () => { + expect(contractInvocation).toBeInstanceOf(ContractInvocation); + expect(contractInvocation.getId()).toBe( + VALID_CONTRACT_INVOCATION_MODEL.contract_invocation_id, + ); + }); + + it("creates the contract invocation", async () => { + expect( + Coinbase.apiClients.contractInvocation!.createContractInvocation, + ).toHaveBeenCalledWith(walletAddress.getWalletId(), walletAddress.getId(), { + abi: VALID_CONTRACT_INVOCATION_MODEL.abi, + args: VALID_CONTRACT_INVOCATION_MODEL.args, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contract_address: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }); + expect( + Coinbase.apiClients.contractInvocation!.createContractInvocation, + ).toHaveBeenCalledTimes(1); + }); + + it("broadcasts the contract invocation", async () => { + expect( + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation, + ).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + VALID_CONTRACT_INVOCATION_MODEL.contract_invocation_id, + { + signed_payload: expectedSignedPayload, + }, + ); + + expect( + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe("when no key is loaded", () => { + beforeEach(() => { + walletAddress = new WalletAddress(addressModel); + }); + + it("throws an error", async () => { + await expect( + walletAddress.invokeContract({ + abi: MINT_NFT_ABI, + args: MINT_NFT_ARGS, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }), + ).rejects.toThrow(Error); + }); + }); + + describe("when it fails to create a contract invocation", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.createContractInvocation = + mockReturnRejectedValue( + new APIError({ + response: { + status: 400, + data: { + code: "malformed_request", + message: "failed to create contract invocation: invalid abi", + }, + }, + } as AxiosError), + ); + }); + + it("throws an error", async () => { + await expect( + walletAddress.invokeContract({ + abi: { invalid_abi: "abi" }, + args: MINT_NFT_ARGS, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }), + ).rejects.toThrow(APIError); + }); + }); + + describe("when it fails to broadcast a contract invocation", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.createContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + Coinbase.apiClients.contractInvocation!.broadcastContractInvocation = + mockReturnRejectedValue( + new APIError({ + response: { + status: 400, + data: { + code: "invalid_signed_payload", + message: "failed to broadcast contract invocation: invalid signed payload", + }, + }, + } as AxiosError), + ); + }); + + it("throws an error", async () => { + await expect( + walletAddress.invokeContract({ + abi: MINT_NFT_ABI, + args: MINT_NFT_ARGS, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }), + ).rejects.toThrow(APIError); + }); + }); + }); + + describe("when using a server-signer", () => { + let contractInvocation; + + beforeEach(async () => { + Coinbase.useServerSigner = true; + + walletAddress = new WalletAddress(addressModel); + }); + + describe("when it is successful", () => { + beforeEach(async () => { + Coinbase.apiClients.contractInvocation!.createContractInvocation = mockReturnValue({ + ...VALID_CONTRACT_INVOCATION_MODEL, + address_id: walletAddress.getId(), + wallet_id: walletAddress.getWalletId(), + }); + + contractInvocation = await walletAddress.invokeContract({ + abi: MINT_NFT_ABI, + args: MINT_NFT_ARGS, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }); + }); + + it("returns a pending contract invocation", async () => { + expect(contractInvocation).toBeInstanceOf(ContractInvocation); + expect(contractInvocation.getId()).toBe( + VALID_CONTRACT_INVOCATION_MODEL.contract_invocation_id, + ); + expect(contractInvocation.getStatus()).toBe(TransactionStatus.PENDING); + }); + + it("creates a contract invocation", async () => { + expect( + Coinbase.apiClients.contractInvocation!.createContractInvocation, + ).toHaveBeenCalledWith(walletAddress.getWalletId(), walletAddress.getId(), { + abi: VALID_CONTRACT_INVOCATION_MODEL.abi, + args: VALID_CONTRACT_INVOCATION_MODEL.args, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contract_address: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }); + expect( + Coinbase.apiClients.contractInvocation!.createContractInvocation, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe("when creating a contract invocation fails", () => { + beforeEach(() => { + Coinbase.apiClients.contractInvocation!.createContractInvocation = + mockReturnRejectedValue( + new APIError({ + response: { + status: 400, + data: { + code: "malformed_request", + message: "failed to create contract invocation: invalid abi", + }, + }, + } as AxiosError), + ); + }); + + it("throws an error", async () => { + await expect( + walletAddress.invokeContract({ + abi: { invalid_abi: "abi" }, + args: MINT_NFT_ARGS, + method: VALID_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_CONTRACT_INVOCATION_MODEL.contract_address, + }), + ).rejects.toThrow(APIError); + }); + }); + }); + }); + + describe("#createPayloadSignature", () => { + let key = ethers.Wallet.createRandom(); + let addressModel: AddressModel; + let walletAddress: WalletAddress; + let unsignedPayload = VALID_PAYLOAD_SIGNATURE_MODEL.unsigned_payload; + let signature: string; + + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("when not using a server-signer", () => { + beforeEach(() => { + addressModel = newAddressModel(randomUUID(), randomUUID(), Coinbase.networks.BaseSepolia); + walletAddress = new WalletAddress(addressModel, key as unknown as ethers.Wallet); + signature = key.signingKey.sign(unsignedPayload).serialized; + Coinbase.useServerSigner = false; + }); + + it("should successfully create a payload signature", async () => { + Coinbase.apiClients.address!.createPayloadSignature = mockReturnValue( + VALID_PAYLOAD_SIGNATURE_MODEL, + ); + + const payloadSignature = await walletAddress.createPayloadSignature(unsignedPayload); + + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + unsigned_payload: unsignedPayload, + signature, + }, + ); + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledTimes(1); + expect(payloadSignature).toBeInstanceOf(PayloadSignature); + }); + + it("should throw an error when no key is loaded", async () => { + walletAddress = new WalletAddress(addressModel); + + expect(async () => { + await walletAddress.createPayloadSignature(unsignedPayload); + }).rejects.toThrow(Error); + }); + + it("should throw an APIError when the API call to create a payload signature fails", async () => { + Coinbase.apiClients.address!.createPayloadSignature = mockReturnRejectedValue( + new APIError("Failed to create payload signature"), + ); + + expect(async () => { + await walletAddress.createPayloadSignature(unsignedPayload); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + unsigned_payload: unsignedPayload, + signature, + }, + ); + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledTimes(1); + }); + }); - describe(".setKey", () => { - it("should set the key successfully", () => { - key = ethers.Wallet.createRandom(); - const newAddress = new WalletAddress(VALID_ADDRESS_MODEL, undefined); - expect(() => { - newAddress.setKey(key); - }).not.toThrow(Error); + describe("when using a server-signer", () => { + beforeEach(() => { + addressModel = newAddressModel(randomUUID(), randomUUID(), Coinbase.networks.BaseSepolia); + walletAddress = new WalletAddress(addressModel); + Coinbase.useServerSigner = true; }); - it("should not set the key successfully", () => { - key = ethers.Wallet.createRandom(); - const newAddress = new WalletAddress(VALID_ADDRESS_MODEL, key); - expect(() => { - newAddress.setKey(key); - }).toThrow(Error); + + it("should successfully create a payload signature", async () => { + Coinbase.apiClients.address!.createPayloadSignature = mockReturnValue( + VALID_PAYLOAD_SIGNATURE_MODEL, + ); + + const payloadSignature = await walletAddress.createPayloadSignature(unsignedPayload); + + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + unsigned_payload: unsignedPayload, + }, + ); + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledTimes(1); + expect(payloadSignature).toBeInstanceOf(PayloadSignature); + }); + + it("should throw an APIError when the API call to create a payload signature fails", async () => { + Coinbase.apiClients.address!.createPayloadSignature = mockReturnRejectedValue( + new APIError("Failed to create payload signature"), + ); + + expect(async () => { + await walletAddress.createPayloadSignature(unsignedPayload); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + { + unsigned_payload: unsignedPayload, + }, + ); + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledTimes(1); }); }); }); + + describe("#getPayloadSignature", () => { + let key = ethers.Wallet.createRandom(); + let addressModel: AddressModel; + let walletAddress: WalletAddress; + let payloadSignatureId = VALID_PAYLOAD_SIGNATURE_MODEL.payload_signature_id; + + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + }); + + beforeEach(() => { + addressModel = newAddressModel(randomUUID(), randomUUID(), Coinbase.networks.BaseSepolia); + walletAddress = new WalletAddress(addressModel, key as unknown as ethers.Wallet); + Coinbase.useServerSigner = false; + jest.clearAllMocks(); + }); + + it("should successfully get the payload signature", async () => { + Coinbase.apiClients.address!.getPayloadSignature = mockReturnValue( + VALID_PAYLOAD_SIGNATURE_MODEL, + ); + + const payloadSignature = await walletAddress.getPayloadSignature(payloadSignatureId); + + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + payloadSignatureId, + ); + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledTimes(1); + expect(payloadSignature).toBeInstanceOf(PayloadSignature); + }); + + it("should throw an APIError when the API call to get the payload signature fails", async () => { + Coinbase.apiClients.address!.getPayloadSignature = mockReturnRejectedValue( + new APIError("Failed to get payload signature"), + ); + + expect(async () => { + await walletAddress.getPayloadSignature(payloadSignatureId); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledWith( + walletAddress.getWalletId(), + walletAddress.getId(), + payloadSignatureId, + ); + expect(Coinbase.apiClients.address!.getPayloadSignature).toHaveBeenCalledTimes(1); + }); + }); + + describe("#listPayloadSignatures", () => { + let key = ethers.Wallet.createRandom(); + let addressModel: AddressModel; + let walletAddress: WalletAddress; + + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + }); + + beforeEach(() => { + addressModel = newAddressModel(randomUUID(), randomUUID(), Coinbase.networks.BaseSepolia); + walletAddress = new WalletAddress(addressModel, key as unknown as ethers.Wallet); + Coinbase.useServerSigner = false; + jest.clearAllMocks(); + }); + + it("should successfully list payload signatures", async () => { + Coinbase.apiClients.address!.listPayloadSignatures = mockReturnValue( + VALID_PAYLOAD_SIGNATURE_LIST, + ); + + const payloadSignatures = await walletAddress.listPayloadSignatures(); + + expect(Coinbase.apiClients.address!.listPayloadSignatures).toHaveBeenCalledTimes(1); + expect(payloadSignatures).toHaveLength(VALID_PAYLOAD_SIGNATURE_LIST.data.length); + }); + + it("should throw an APIError when the API call to list payload signatures fails", async () => { + Coinbase.apiClients.address!.listPayloadSignatures = mockReturnRejectedValue( + new APIError("Failed to list payload signatures"), + ); + + expect(async () => { + await walletAddress.listPayloadSignatures(); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.listPayloadSignatures).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/tests/wallet_test.ts b/src/tests/wallet_test.ts index cdbe49cc..1a55c345 100644 --- a/src/tests/wallet_test.ts +++ b/src/tests/wallet_test.ts @@ -44,12 +44,18 @@ import { externalAddressApiMock, stakeApiMock, walletStakeApiMock, + MINT_NFT_ABI, + MINT_NFT_ARGS, + VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL, + VALID_SIGNED_CONTRACT_INVOCATION_MODEL, } from "./utils"; import { Trade } from "../coinbase/trade"; import { WalletAddress } from "../coinbase/address/wallet_address"; import { StakingOperation } from "../coinbase/staking_operation"; import { StakingReward } from "../coinbase/staking_reward"; import { StakingBalance } from "../coinbase/staking_balance"; +import { PayloadSignature } from "../coinbase/payload_signature"; +import { ContractInvocation } from "../coinbase/contract_invocation"; describe("Wallet Class", () => { let wallet: Wallet; @@ -504,7 +510,7 @@ describe("Wallet Class", () => { }); describe(".createTransfer", () => { - let weiAmount, destination, intervalSeconds, timeoutSeconds; + let weiAmount, destination; let balanceModel: BalanceModel; beforeEach(() => { @@ -512,8 +518,6 @@ describe("Wallet Class", () => { const key = ethers.Wallet.createRandom(); weiAmount = new Decimal("5"); destination = new WalletAddress(VALID_ADDRESS_MODEL, key as unknown as ethers.Wallet); - intervalSeconds = 0.2; - timeoutSeconds = 10; Coinbase.apiClients.externalAddress = externalAddressApiMock; Coinbase.apiClients.asset = assetsApiMock; Coinbase.apiClients.asset!.getAsset = getAssetMock(); @@ -566,8 +570,6 @@ describe("Wallet Class", () => { amount: weiAmount, assetId: Coinbase.assets.Wei, destination, - timeoutSeconds, - intervalSeconds, }), ).rejects.toThrow(APIError); }); @@ -582,8 +584,6 @@ describe("Wallet Class", () => { amount: weiAmount, assetId: Coinbase.assets.Wei, destination, - timeoutSeconds, - intervalSeconds, }), ).rejects.toThrow(APIError); }); @@ -595,8 +595,6 @@ describe("Wallet Class", () => { amount: insufficientAmount, assetId: Coinbase.assets.Wei, destination, - timeoutSeconds, - intervalSeconds, }), ).rejects.toThrow(ArgumentError); }); @@ -609,8 +607,6 @@ describe("Wallet Class", () => { amount: weiAmount, assetId: Coinbase.assets.Wei, destination, - timeoutSeconds, - intervalSeconds, }); expect(Coinbase.apiClients.transfer!.createTransfer).toHaveBeenCalledTimes(1); @@ -624,6 +620,107 @@ describe("Wallet Class", () => { }); }); + describe("#invokeContract", () => { + let expectedInvocation; + let options = { + abi: MINT_NFT_ABI, + args: MINT_NFT_ARGS, + method: VALID_SIGNED_CONTRACT_INVOCATION_MODEL.method, + contractAddress: VALID_SIGNED_CONTRACT_INVOCATION_MODEL.contract_address, + }; + + beforeEach(() => { + expectedInvocation = ContractInvocation.fromModel(VALID_SIGNED_CONTRACT_INVOCATION_MODEL); + + wallet.getDefaultAddress()!.invokeContract = jest.fn().mockResolvedValue(expectedInvocation); + }); + + it("successfully invokes a contract on the default address", async () => { + const contractInvocation = await wallet.invokeContract(options); + + expect(wallet.getDefaultAddress()!.invokeContract).toHaveBeenCalledTimes(1); + expect(wallet.getDefaultAddress()!.invokeContract).toHaveBeenCalledWith(options); + + expect(contractInvocation).toBeInstanceOf(ContractInvocation); + expect(contractInvocation).toEqual(expectedInvocation); + }); + + describe("when the wallet does not have a default address", () => { + let invalidWallet; + + beforeEach(() => (invalidWallet = Wallet.init(walletModel))); + + it("should throw an Error", async () => { + await expect(async () => await invalidWallet.invokeContract(options)).rejects.toThrow( + Error, + ); + }); + }); + }); + + describe("#createPayloadSignature", () => { + let unsignedPayload = VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL.unsigned_payload; + let signature = + "0xa4e14b28d86dfd7bae739d724ba2ffb13b4458d040930b805eea0a4bc2f5251e7901110677d1ef2ec23ef810c755d0bc72cc6472a4cfb3c53ef242c6ba9fa60a1b"; + + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should successfully create a payload signature", async () => { + Coinbase.apiClients.address!.createPayloadSignature = mockReturnValue( + VALID_SIGNED_PAYLOAD_SIGNATURE_MODEL, + ); + + const payloadSignature = await wallet.createPayloadSignature(unsignedPayload); + + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledWith( + wallet.getId(), + wallet.getDefaultAddress()!.getId(), + { + unsigned_payload: unsignedPayload, + signature, + }, + ); + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledTimes(1); + expect(payloadSignature).toBeInstanceOf(PayloadSignature); + }); + + it("should throw an APIError when the API call to create a payload signature fails", async () => { + Coinbase.apiClients.address!.createPayloadSignature = mockReturnRejectedValue( + new APIError("Failed to create payload signature"), + ); + + expect(async () => { + await wallet.createPayloadSignature(unsignedPayload); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledWith( + wallet.getId(), + wallet.getDefaultAddress()!.getId(), + { + unsigned_payload: unsignedPayload, + signature, + }, + ); + expect(Coinbase.apiClients.address!.createPayloadSignature).toHaveBeenCalledTimes(1); + }); + + it("should throw an Error when the wallet does not have a default address", async () => { + const invalidWallet = Wallet.init(walletModel); + + expect(async () => { + await invalidWallet.createPayloadSignature(unsignedPayload); + }).rejects.toThrow(Error); + + expect(Coinbase.apiClients.address!.createPayloadSignature).not.toHaveBeenCalled(); + }); + }); + describe(".create", () => { beforeEach(() => {}); it("should return a Wallet instance", async () => { diff --git a/src/tests/webhook_test.ts b/src/tests/webhook_test.ts index d518e3f1..68982a9b 100644 --- a/src/tests/webhook_test.ts +++ b/src/tests/webhook_test.ts @@ -9,6 +9,7 @@ describe("Webhook", () => { notification_uri: "https://example.com/callback", event_type: "erc20_transfer", event_filters: [{ contract_address: "0x...", from_address: "0x...", to_address: "0x..." }], + signature_header: "example_header", }; beforeEach(() => { @@ -33,7 +34,7 @@ describe("Webhook", () => { }; }); - describe("#init", () => { + describe(".init", () => { it("should throw an error if the model is null", () => { expect(() => Webhook.init(null as any)).toThrow("Webhook model cannot be empty"); }); @@ -44,7 +45,7 @@ describe("Webhook", () => { }); }); - describe(".getId", () => { + describe("#getId", () => { it("should return the ID of the webhook", () => { const webhook = Webhook.init(mockModel); expect(webhook.getId()).toBe("test-id"); @@ -60,7 +61,7 @@ describe("Webhook", () => { }); }); - describe(".getNetworkId", () => { + describe("#getNetworkId", () => { it("should return the network ID of the webhook", () => { const webhook = Webhook.init(mockModel); expect(webhook.getNetworkId()).toBe("test-network"); @@ -76,7 +77,7 @@ describe("Webhook", () => { }); }); - describe(".getNotificationURI", () => { + describe("#getNotificationURI", () => { it("should return the notification URI of the webhook", () => { const webhook = Webhook.init(mockModel); expect(webhook.getNotificationURI()).toBe("https://example.com/callback"); @@ -92,7 +93,7 @@ describe("Webhook", () => { }); }); - describe(".getEventType", () => { + describe("#getEventType", () => { it("should return the event type of the webhook", () => { const webhook = Webhook.init(mockModel); expect(webhook.getEventType()).toBe("erc20_transfer"); @@ -108,7 +109,7 @@ describe("Webhook", () => { }); }); - describe(".getEventFilters", () => { + describe("#getEventFilters", () => { it("should return the event filters of the webhook", () => { const webhook = Webhook.init(mockModel); expect(webhook.getEventFilters()).toEqual([ @@ -126,27 +127,58 @@ describe("Webhook", () => { }); }); - describe("#create", () => { + describe("#getSignatureHeader", () => { + it("should return the signature header of the webhook", () => { + const webhook = Webhook.init(mockModel); + expect(webhook.getSignatureHeader()).toBe("example_header"); + }); + + it("should return undefined if the signature header is not set", () => { + const modelWithoutSignatureHeader: WebhookModel = { + ...mockModel, + signature_header: undefined, + }; + const webhook = Webhook.init(modelWithoutSignatureHeader); + expect(webhook.getSignatureHeader()).toBeUndefined(); + }); + }); + + describe(".create", () => { it("should create a new webhook", async () => { - const webhook = await Webhook.create( - "test-network", - "https://example.com/callback", - "erc20_transfer", - [{ contract_address: "0x...", from_address: "0x...", to_address: "0x..." }], - ); + const webhook = await Webhook.create({ + networkId: "test-network", + notificationUri: "https://example.com/callback", + eventType: "erc20_transfer", + eventFilters: [{ contract_address: "0x...", from_address: "0x...", to_address: "0x..." }], + signatureHeader: "example_header", + }); expect(Coinbase.apiClients.webhook!.createWebhook).toHaveBeenCalledWith({ network_id: "test-network", notification_uri: "https://example.com/callback", event_type: "erc20_transfer", event_filters: [{ contract_address: "0x...", from_address: "0x...", to_address: "0x..." }], + signature_header: "example_header", }); expect(webhook).toBeInstanceOf(Webhook); expect(webhook.getId()).toBe("test-id"); }); + + it("should throw an error if creation fails", async () => { + Coinbase.apiClients.webhook!.createWebhook = jest + .fn() + .mockRejectedValue(new Error("Failed to create webhook")); + await expect( + Webhook.create({ + networkId: "test-network", + notificationUri: "https://example.com/callback", + eventType: "erc20_transfer", + }), + ).rejects.toThrow("Failed to create webhook"); + }); }); - describe("#list", () => { + describe(".list", () => { it("should list all webhooks", async () => { const webhooks = await Webhook.list(); @@ -180,7 +212,7 @@ describe("Webhook", () => { }); }); - describe(".update", () => { + describe("#update", () => { it("should update the webhook notification URI", async () => { const webhook = Webhook.init(mockModel); await webhook.update("https://new-url.com/callback"); @@ -194,7 +226,7 @@ describe("Webhook", () => { }); }); - describe(".delete", () => { + describe("#delete", () => { it("should delete the webhook and set the model to null", async () => { const webhook = Webhook.init(mockModel); await webhook.delete(); @@ -205,12 +237,12 @@ describe("Webhook", () => { }); }); - describe(".toString", () => { + describe("#toString", () => { it("should return a string representation of the webhook", () => { const webhook = Webhook.init(mockModel); const stringRepresentation = webhook.toString(); expect(stringRepresentation).toBe( - `Webhook { id: 'test-id', network_id: 'test-network', event_type: 'erc20_transfer', event_filter: '[{"contract_address":"0x...","from_address":"0x...","to_address":"0x..."}] notification_uri: 'https://example.com/callback' }`, + `Webhook { id: 'test-id', networkId: 'test-network', eventType: 'erc20_transfer', eventFilter: [{"contract_address":"0x...","from_address":"0x...","to_address":"0x..."}], notificationUri: 'https://example.com/callback', signatureHeader: 'example_header' }`, ); }); }); diff --git a/yarn.lock b/yarn.lock index c691a9d7..15256f9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,7 +28,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz" integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": version "7.24.5" resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz" integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== @@ -569,7 +569,7 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.0.0", "@jest/types@^29.6.3": +"@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== @@ -605,14 +605,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -621,12 +613,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@noble/curves@~1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz" - integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: - "@noble/hashes" "1.4.0" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@noble/curves@1.2.0": version "1.2.0" @@ -635,16 +628,23 @@ dependencies: "@noble/hashes" "1.3.2" -"@noble/hashes@^1.2.0", "@noble/hashes@~1.4.0", "@noble/hashes@1.4.0": +"@noble/curves@~1.4.0": version "1.4.0" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" - integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@1.4.0", "@noble/hashes@^1.2.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -653,7 +653,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -757,7 +757,7 @@ dependencies: "@babel/types" "^7.20.7" -"@types/eslint@^8.56.5", "@types/eslint@>=8.0.0": +"@types/eslint@^8.56.5": version "8.56.10" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz" integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ== @@ -874,7 +874,7 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.8.0": +"@typescript-eslint/parser@^7.8.0": version "7.8.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz" integrity sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ== @@ -903,7 +903,7 @@ debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@^7.2.0", "@typescript-eslint/types@7.8.0": +"@typescript-eslint/types@7.8.0", "@typescript-eslint/types@^7.2.0": version "7.8.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz" integrity sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw== @@ -958,7 +958,7 @@ acorn-walk@^8.1.1: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.4.1, acorn@^8.9.0: +acorn@^8.4.1, acorn@^8.9.0: version "8.11.3" resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -1069,7 +1069,7 @@ axios-retry@^4.4.1: dependencies: is-retry-allowed "^2.2.0" -axios@^1.6.8, "axios@>= 0.17.0", "axios@0.x || 1.x": +axios@^1.6.8: version "1.6.8" resolved "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz" integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== @@ -1078,7 +1078,7 @@ axios@^1.6.8, "axios@>= 0.17.0", "axios@0.x || 1.x": form-data "^4.0.0" proxy-from-env "^1.1.0" -babel-jest@^29.0.0, babel-jest@^29.7.0: +babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== @@ -1209,7 +1209,7 @@ brorand@^1.1.0: resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserslist@^4.22.2, "browserslist@>= 4.21.0": +browserslist@^4.22.2: version "4.23.0" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -1360,16 +1360,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -1556,7 +1556,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@*, eslint-config-prettier@^9.1.0: +eslint-config-prettier@^9.1.0: version "9.1.0" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz" integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== @@ -1597,7 +1597,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.56.0, eslint@^8.57.0, eslint@>=7.0.0, eslint@>=8.0.0: +eslint@^8.57.0: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -1744,7 +1744,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1782,15 +1782,7 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -2021,7 +2013,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@2: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2354,7 +2346,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@*, jest-resolve@^29.7.0: +jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -2498,7 +2490,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.0.0, jest@^29.7.0: +jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -2651,7 +2643,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@^1.1.1, make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -2722,28 +2714,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.5: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2964,7 +2935,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^3.2.5, prettier@>=3.0.0: +prettier@^3.2.5: version "3.2.5" resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== @@ -3102,12 +3073,7 @@ secp256k1@^5.0.0: node-addon-api "^5.0.0" node-gyp-build "^4.2.0" -semver@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -3205,13 +3171,6 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -3229,6 +3188,13 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -3335,7 +3301,7 @@ ts-jest@^29.1.2: semver "^7.5.3" yargs-parser "^21.0.1" -ts-node@^10.9.2, ts-node@>=9.0.0: +ts-node@^10.9.2: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -3354,16 +3320,16 @@ ts-node@^10.9.2, ts-node@>=9.0.0: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@^2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - tslib@2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -3401,7 +3367,7 @@ typeforce@^1.11.5: resolved "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== -typescript@^5.4.5, typescript@>=2.7, typescript@>=4.2.0, "typescript@>=4.3 <6", "typescript@4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x": +typescript@^5.4.5: version "5.4.5" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==