@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
569 lines • 27.8 kB
JavaScript
/**
* @module Transfers
*/
import { normalizeClientOptionsWithAuth } from "../../../../BaseClient.js";
import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js";
import * as core from "../../../../core/index.js";
import * as environments from "../../../../environments.js";
import { handleNonStatusCodeError } from "../../../../errors/handleNonStatusCodeError.js";
import * as errors from "../../../../errors/index.js";
import * as CoinbaseApi from "../../../index.js";
/**
* **Transfers** represent both the request and execution of fund transfers from a source to a target. They provide upfront fee quotes and track the complete lifecycle from initiation through completion, failure, or reversal.
* ## Fee Quotes
* Every transfer provides a comprehensive fee quote in the `fees` array. This allows you to show users exactly what they'll pay before any money moves.
*
* To review fees before execution:
* 1. Create a transfer with `execute: false`
* 2. Review the `fees` array in the response
* 3. Call `POST /transfers/{transferId}/execute` when ready to proceed
*
*
* For automatic execution without fee review, create a transfer with `execute: true`.
*
* **Fee Expiration**: Fee quotes are valid for a limited time (typically 10-15 minutes from creation). The `expiresAt` field shows exactly when the fee quote will expire. If you don't execute before this time, you'll need to create a new transfer to get updated fees.
* ## Fees
* Transfer fees vary by source, target, amount and transfer type:
* * **Bank fees** - Traditional banking fees for depositing funds (e.g., $15.00 wire transfer fee)
* * **Conversion fees** - Fees for exchanging between different assets
* * **Network fees** - Onchain transaction costs to complete the transfer (e.g., ETH gas fees)
*
* All fees are disclosed upfront in the `fees` array when you create a transfer.
* ## Transfer Lifecycle
* When you create a transfer, it will be in one of these statuses that determine what action you need to take:
* * **`quoted`** - Transfer is ready but requires manual execution via the `/execute` endpoint
* * **`processing`** - Transfer is being executed (no action needed - poll for completion)
* * **`completed`** - Transfer completed successfully
* * **`failed`** - Transfer failed (see `failureReason` for details)
* ## Execution Control
* * **`execute: true`**: Transfer will automatically attempt to execute
* * **`execute: false`**: Transfer will be created in `quoted` status and you must call the `/execute` endpoint. Use this to obtain a fee quote or validate a transfer destination before deciding whether to execute the Transfer.
* ## Sources and Targets
* * A **source** can be an Account or a Payment Method
* * A **target** can be an Account, Payment Method, Onchain Address, or Email Address
* ## Transfer Execution
* When a transfer reaches `completed` status, it contains the final execution details that delivered funds to the target and completion timestamps.
* ## Failure Reasons
* When a transfer fails, the `failureReason` field provides a human-readable description of what went wrong.
* Common failure reasons include:
* * "Insufficient balance to complete this transfer."
* * "The recipient address is invalid for the selected network."
* * "The recipient address failed security validation checks."
* * "Unable to send to this recipient."
*
* Failure reason is only present when the transfer's status is `failed`.
*/
export class TransfersClient {
_options;
constructor(options) {
this._options = normalizeClientOptionsWithAuth(options);
}
/**
* List transfers for your organization. Use this to view and monitor your transfer activity.
*
* **Status Filtering**: Filter by specific status to efficiently manage transfers:
* * `?status=processing` - Monitor active transfers.
* * `?status=quoted` - Find transfers awaiting execution.
* * `?status=failed` - Review failed transfers for troubleshooting.
* * `?status=completed` - Find completed transfers.
*
* **Account Filtering**: Filter by account ID to find transfers involving a specific account:
* * `?accountId=<ID>` - All transfers where the account is either source or target (OR semantics).
* * `?sourceAccountId=<ID>` - Only transfers where the account is the source (outbound).
* * `?targetAccountId=<ID>` - Only transfers where the account is the target (inbound).
* Providing `accountId` together with `sourceAccountId` or `targetAccountId` is a validation error and returns HTTP 400.
*
* **Date Range Filtering**: Filter by creation or last-updated time for reconciliation:
* * `?createdAfter=2026-01-01T00:00:00Z&createdBefore=2026-01-31T23:59:59Z` - Transfers created within a date range.
* * `?updatedAfter=2026-01-01T00:00:00Z` - Transfers updated since a given time. Useful for incremental sync.
*
* **Asset Filtering**: Filter by source or target asset symbol:
* * `?sourceAsset=usd` - Transfers funded from a USD account.
* * `?targetAsset=usdc` - Transfers delivering USDC to the target.
*
* **Other Filters**:
* * `?sourceAddress=0x...` - Transfers from a specific on-chain source address.
* * `?targetAddress=0x...` - Transfers to a specific on-chain destination address.
* * `?targetEmail=user@example.com` - Transfers to a specific email recipient.
* * `?transferId=transfer_...` - Look up a single transfer by ID; bypasses pagination.
*
* @param {CoinbaseApi.ListTransfersRequest} request
* @param {TransfersClient.RequestOptions} requestOptions - Request-specific configuration.
*
* @throws CoinbaseApi.BadRequestError
*
* @example
* ```ts
* await client.transfers.listTransfers({
* accountId: "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
* sourceAccountId: "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
* targetAccountId: "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
* createdAfter: "2026-01-01T00:00:00Z",
* createdBefore: "2026-01-31T23:59:59Z",
* updatedAfter: "2026-01-01T00:00:00Z",
* updatedBefore: "2026-01-31T23:59:59Z",
* sourceAsset: "usd",
* targetAsset: "usdc",
* sourceAddress: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
* targetAddress: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
* targetEmail: "user@example.com",
* transferId: "transfer_af2937b0-9846-4fe7-bfe9-ccc22d935114",
* pageToken: "eyJsYXN0X2lkIjogImFiYzEyMyIsICJ0aW1lc3RhbXAiOiAxNzA3ODIzNzAxfQ=="
* })
* ```
*/
listTransfers(request = {}, requestOptions) {
return core.HttpResponsePromise.fromPromise(this.__listTransfers(request, requestOptions));
}
async __listTransfers(request = {}, requestOptions) {
const _metadata = {
security: [{ apiKeyAuth: [] }],
method: "GET",
path: "v2/transfers",
operationId: "endpoint_transfers.listTransfers",
};
const { status, accountId, sourceAccountId, targetAccountId, createdAfter, createdBefore, updatedAfter, updatedBefore, sourceAsset, targetAsset, sourceAddress, targetAddress, targetEmail, transferId, pageSize, pageToken, } = request;
const _queryParams = {
status: status != null ? status : undefined,
accountId,
sourceAccountId,
targetAccountId,
createdAfter: createdAfter != null ? createdAfter : undefined,
createdBefore: createdBefore != null ? createdBefore : undefined,
updatedAfter: updatedAfter != null ? updatedAfter : undefined,
updatedBefore: updatedBefore != null ? updatedBefore : undefined,
sourceAsset,
targetAsset,
sourceAddress,
targetAddress,
targetEmail,
transferId,
pageSize,
pageToken,
};
const _authRequest = await this._options.authProvider.getAuthRequest({
endpointMetadata: _metadata,
clientOptions: this._options,
request: {
method: "GET",
},
});
const _headers = mergeHeaders(_authRequest.headers, this._options?.headers, requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join((await core.Supplier.get(this._options.baseUrl)) ??
(await core.Supplier.get(this._options.environment)) ??
environments.CoinbaseApiEnvironment.Default, "v2/transfers"),
method: "GET",
headers: _headers,
queryString: core.url
.queryBuilder()
.addMany(_queryParams)
.mergeAdditional(requestOptions?.queryParams)
.build(),
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
abortSignal: requestOptions?.abortSignal,
endpointMetadata: _metadata,
fetchFn: this._options?.fetch,
logging: this._options.logging,
});
if (_response.ok) {
return { data: _response.body, rawResponse: _response.rawResponse };
}
if (_response.error.reason === "status-code") {
switch (_response.error.statusCode) {
case 400:
throw new CoinbaseApi.BadRequestError(_response.error.body, _response.rawResponse);
default:
throw new errors.CoinbaseApiError({
statusCode: _response.error.statusCode,
body: _response.error.body,
rawResponse: _response.rawResponse,
});
}
}
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v2/transfers");
}
/**
* Create a new transfer to move funds from a source to a target.
* All transfers first transition to `quoted`. If `execute: false`, the transfer stays quoted until you call `/v2/transfers/{transferId}/execute`.
* If `execute: true`, quoted status emits momentarily before the transfer moves to `processing`, where execution proceeds. Subscribe to the transfers webhook to follow progress in real time instead of polling.
*
* @param {CoinbaseApi.TransferRequest} request
* @param {TransfersClient.RequestOptions} requestOptions - Request-specific configuration.
*
* @throws CoinbaseApi.BadRequestError
* @throws CoinbaseApi.ForbiddenError
* @throws CoinbaseApi.UnprocessableEntityError
*
* @example
* ```ts
* await client.transfers.createTransfer({
* idempotencyKey: "8e03978e-40d5-43e8-bc93-6894a57f9324",
* source: {
* accountId: "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
* asset: "usd"
* },
* target: {
* address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
* network: "base",
* asset: "usdc"
* },
* amount: "100.00",
* asset: "usd",
* validateOnly: false,
* execute: false,
* metadata: {
* "invoiceId": "12345",
* "reference": "Payment for invoice #12345"
* },
* travelRule: {
* isSelf: false,
* isIntermediary: true,
* originator: {
* financialInstitution: "PayPal, Inc.",
* name: "John Doe",
* address: {
* line1: "123 Main St",
* line2: "Unit 201",
* city: "San Francisco",
* state: "California",
* postCode: "94105",
* countryCode: "US"
* },
* personalId: "123-45-6789",
* dateOfBirth: {
* day: "15",
* month: "08",
* year: "1990"
* }
* },
* beneficiary: {
* name: "Jane Smith",
* address: {
* line1: "456 Oak Ave",
* city: "Paris",
* postCode: "75001",
* countryCode: "FR"
* },
* walletType: "custodial"
* }
* }
* })
* ```
*/
createTransfer(request, requestOptions) {
return core.HttpResponsePromise.fromPromise(this.__createTransfer(request, requestOptions));
}
async __createTransfer(request, requestOptions) {
const _metadata = {
security: [{ apiKeyAuth: [] }],
method: "POST",
path: "v2/transfers",
operationId: "endpoint_transfers.createTransfer",
headers: ["X-Idempotency-Key"],
};
const { idempotencyKey, ..._body } = request;
const _authRequest = await this._options.authProvider.getAuthRequest({
endpointMetadata: _metadata,
clientOptions: this._options,
request: {
method: "POST",
body: _body,
},
});
const _headers = mergeHeaders(_authRequest.headers, this._options?.headers, mergeOnlyDefinedHeaders({ "X-Idempotency-Key": idempotencyKey }), requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join((await core.Supplier.get(this._options.baseUrl)) ??
(await core.Supplier.get(this._options.environment)) ??
environments.CoinbaseApiEnvironment.Default, "v2/transfers"),
method: "POST",
headers: _headers,
contentType: "application/json",
queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
requestType: "json",
body: _body,
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
abortSignal: requestOptions?.abortSignal,
endpointMetadata: _metadata,
fetchFn: this._options?.fetch,
logging: this._options.logging,
});
if (_response.ok) {
return { data: _response.body, rawResponse: _response.rawResponse };
}
if (_response.error.reason === "status-code") {
switch (_response.error.statusCode) {
case 400:
throw new CoinbaseApi.BadRequestError(_response.error.body, _response.rawResponse);
case 403:
throw new CoinbaseApi.ForbiddenError(_response.error.body, _response.rawResponse);
case 422:
throw new CoinbaseApi.UnprocessableEntityError(_response.error.body, _response.rawResponse);
default:
throw new errors.CoinbaseApiError({
statusCode: _response.error.statusCode,
body: _response.error.body,
rawResponse: _response.rawResponse,
});
}
}
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v2/transfers");
}
/**
* Get a transfer by its ID.
*
* @param {CoinbaseApi.GetTransferByIdRequest} request
* @param {TransfersClient.RequestOptions} requestOptions - Request-specific configuration.
*
* @throws CoinbaseApi.NotFoundError
*
* @example
* ```ts
* await client.transfers.getTransferById({
* transferId: "transfer_af2937b0-9846-4fe7-bfe9-ccc22d935114"
* })
* ```
*/
getTransferById(request, requestOptions) {
return core.HttpResponsePromise.fromPromise(this.__getTransferById(request, requestOptions));
}
async __getTransferById(request, requestOptions) {
const _metadata = {
security: [{ apiKeyAuth: [] }],
method: "GET",
path: "v2/transfers/{transferId}",
operationId: "endpoint_transfers.getTransferById",
};
const { transferId } = request;
const _authRequest = await this._options.authProvider.getAuthRequest({
endpointMetadata: _metadata,
clientOptions: this._options,
request: {
method: "GET",
},
});
const _headers = mergeHeaders(_authRequest.headers, this._options?.headers, requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join((await core.Supplier.get(this._options.baseUrl)) ??
(await core.Supplier.get(this._options.environment)) ??
environments.CoinbaseApiEnvironment.Default, `v2/transfers/${core.url.encodePathParam(transferId)}`),
method: "GET",
headers: _headers,
queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
abortSignal: requestOptions?.abortSignal,
endpointMetadata: _metadata,
fetchFn: this._options?.fetch,
logging: this._options.logging,
});
if (_response.ok) {
return { data: _response.body, rawResponse: _response.rawResponse };
}
if (_response.error.reason === "status-code") {
switch (_response.error.statusCode) {
case 404:
throw new CoinbaseApi.NotFoundError(_response.error.body, _response.rawResponse);
default:
throw new errors.CoinbaseApiError({
statusCode: _response.error.statusCode,
body: _response.error.body,
rawResponse: _response.rawResponse,
});
}
}
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v2/transfers/{transferId}");
}
/**
* Executes a transfer which was created using the Create a transfer endpoint.
*
* @param {CoinbaseApi.ExecuteFundTransferRequest} request
* @param {TransfersClient.RequestOptions} requestOptions - Request-specific configuration.
*
* @throws CoinbaseApi.BadRequestError
* @throws CoinbaseApi.UnauthorizedError
* @throws CoinbaseApi.ForbiddenError
* @throws CoinbaseApi.NotFoundError
* @throws CoinbaseApi.UnprocessableEntityError
* @throws CoinbaseApi.TooManyRequestsError
* @throws CoinbaseApi.InternalServerError
* @throws CoinbaseApi.BadGatewayError
* @throws CoinbaseApi.ServiceUnavailableError
*
* @example
* ```ts
* await client.transfers.executeFundTransfer({
* idempotencyKey: "8e03978e-40d5-43e8-bc93-6894a57f9324",
* transferId: "transfer_af2937b0-9846-4fe7-bfe9-ccc22d935114"
* })
* ```
*/
executeFundTransfer(request, requestOptions) {
return core.HttpResponsePromise.fromPromise(this.__executeFundTransfer(request, requestOptions));
}
async __executeFundTransfer(request, requestOptions) {
const _metadata = {
security: [{ apiKeyAuth: [] }],
method: "POST",
path: "v2/transfers/{transferId}/execute",
operationId: "endpoint_transfers.executeFundTransfer",
headers: ["X-Idempotency-Key"],
};
const { transferId, idempotencyKey } = request;
const _authRequest = await this._options.authProvider.getAuthRequest({
endpointMetadata: _metadata,
clientOptions: this._options,
request: {
method: "POST",
},
});
const _headers = mergeHeaders(_authRequest.headers, this._options?.headers, mergeOnlyDefinedHeaders({ "X-Idempotency-Key": idempotencyKey }), requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join((await core.Supplier.get(this._options.baseUrl)) ??
(await core.Supplier.get(this._options.environment)) ??
environments.CoinbaseApiEnvironment.Default, `v2/transfers/${core.url.encodePathParam(transferId)}/execute`),
method: "POST",
headers: _headers,
queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
abortSignal: requestOptions?.abortSignal,
endpointMetadata: _metadata,
fetchFn: this._options?.fetch,
logging: this._options.logging,
});
if (_response.ok) {
return { data: _response.body, rawResponse: _response.rawResponse };
}
if (_response.error.reason === "status-code") {
switch (_response.error.statusCode) {
case 400:
throw new CoinbaseApi.BadRequestError(_response.error.body, _response.rawResponse);
case 401:
throw new CoinbaseApi.UnauthorizedError(_response.error.body, _response.rawResponse);
case 403:
throw new CoinbaseApi.ForbiddenError(_response.error.body, _response.rawResponse);
case 404:
throw new CoinbaseApi.NotFoundError(_response.error.body, _response.rawResponse);
case 422:
throw new CoinbaseApi.UnprocessableEntityError(_response.error.body, _response.rawResponse);
case 429:
throw new CoinbaseApi.TooManyRequestsError(_response.error.body, _response.rawResponse);
case 500:
throw new CoinbaseApi.InternalServerError(_response.error.body, _response.rawResponse);
case 502:
throw new CoinbaseApi.BadGatewayError(_response.error.body, _response.rawResponse);
case 503:
throw new CoinbaseApi.ServiceUnavailableError(_response.error.body, _response.rawResponse);
default:
throw new errors.CoinbaseApiError({
statusCode: _response.error.statusCode,
body: _response.error.body,
rawResponse: _response.rawResponse,
});
}
}
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v2/transfers/{transferId}/execute");
}
/**
* Submit travel rule information for a deposit transfer held pending compliance review.
*
* Required fields vary by jurisdiction and may include originator name, address, date of birth, personal ID, and VASP information.
*
* If the submitted information satisfies all jurisdictional requirements, `status` will be `completed` and the transfer will proceed. Otherwise, `status` will be `incomplete` and `missingFields` will indicate which fields still need to be provided.
*
* @param {CoinbaseApi.DepositTravelRuleRequest} request
* @param {TransfersClient.RequestOptions} requestOptions - Request-specific configuration.
*
* @throws CoinbaseApi.BadRequestError
* @throws CoinbaseApi.NotFoundError
* @throws CoinbaseApi.UnprocessableEntityError
*
* @example
* ```ts
* await client.transfers.submitDepositTravelRule({
* idempotencyKey: "8e03978e-40d5-43e8-bc93-6894a57f9324",
* transferId: "transfer_af2937b0-9846-4fe7-bfe9-ccc22d935114",
* originator: {
* name: "John Doe",
* address: {
* line1: "123 Main St",
* city: "San Francisco",
* state: "CA",
* postCode: "94105",
* countryCode: "US"
* }
* },
* beneficiary: {
* name: "Jane Smith"
* },
* isSelf: false
* })
* ```
*/
submitDepositTravelRule(request, requestOptions) {
return core.HttpResponsePromise.fromPromise(this.__submitDepositTravelRule(request, requestOptions));
}
async __submitDepositTravelRule(request, requestOptions) {
const _metadata = {
security: [{ apiKeyAuth: [] }],
method: "POST",
path: "v2/transfers/{transferId}/travel-rule",
operationId: "endpoint_transfers.submitDepositTravelRule",
headers: ["X-Idempotency-Key"],
};
const { transferId, idempotencyKey, ..._body } = request;
const _authRequest = await this._options.authProvider.getAuthRequest({
endpointMetadata: _metadata,
clientOptions: this._options,
request: {
method: "POST",
body: _body,
},
});
const _headers = mergeHeaders(_authRequest.headers, this._options?.headers, mergeOnlyDefinedHeaders({ "X-Idempotency-Key": idempotencyKey }), requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join((await core.Supplier.get(this._options.baseUrl)) ??
(await core.Supplier.get(this._options.environment)) ??
environments.CoinbaseApiEnvironment.Default, `v2/transfers/${core.url.encodePathParam(transferId)}/travel-rule`),
method: "POST",
headers: _headers,
contentType: "application/json",
queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
requestType: "json",
body: _body,
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
abortSignal: requestOptions?.abortSignal,
endpointMetadata: _metadata,
fetchFn: this._options?.fetch,
logging: this._options.logging,
});
if (_response.ok) {
return {
data: _response.body,
rawResponse: _response.rawResponse,
};
}
if (_response.error.reason === "status-code") {
switch (_response.error.statusCode) {
case 400:
throw new CoinbaseApi.BadRequestError(_response.error.body, _response.rawResponse);
case 404:
throw new CoinbaseApi.NotFoundError(_response.error.body, _response.rawResponse);
case 422:
throw new CoinbaseApi.UnprocessableEntityError(_response.error.body, _response.rawResponse);
default:
throw new errors.CoinbaseApiError({
statusCode: _response.error.statusCode,
body: _response.error.body,
rawResponse: _response.rawResponse,
});
}
}
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v2/transfers/{transferId}/travel-rule");
}
}
//# sourceMappingURL=Client.js.map