@coinbase/cdp-sdk 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +58 -2
  3. package/_cjs/accounts/evm/networkToChainResolver.js +68 -0
  4. package/_cjs/accounts/evm/networkToChainResolver.js.map +1 -0
  5. package/_cjs/accounts/evm/resolveViemClients.js +6 -32
  6. package/_cjs/accounts/evm/resolveViemClients.js.map +1 -1
  7. package/_cjs/accounts/evm/toEvmServerAccount.js +84 -0
  8. package/_cjs/accounts/evm/toEvmServerAccount.js.map +1 -1
  9. package/_cjs/accounts/evm/toEvmSmartAccount.js +93 -0
  10. package/_cjs/accounts/evm/toEvmSmartAccount.js.map +1 -1
  11. package/_cjs/accounts/evm/toNetworkScopedEvmServerAccount.js +19 -7
  12. package/_cjs/accounts/evm/toNetworkScopedEvmServerAccount.js.map +1 -1
  13. package/_cjs/accounts/evm/toNetworkScopedEvmSmartAccount.js +65 -0
  14. package/_cjs/accounts/evm/toNetworkScopedEvmSmartAccount.js.map +1 -1
  15. package/_cjs/accounts/solana/toSolanaAccount.js +20 -0
  16. package/_cjs/accounts/solana/toSolanaAccount.js.map +1 -1
  17. package/_cjs/actions/evm/fund/fund.js +3 -2
  18. package/_cjs/actions/evm/fund/fund.js.map +1 -1
  19. package/_cjs/actions/evm/fund/quoteFund.js +3 -2
  20. package/_cjs/actions/evm/fund/quoteFund.js.map +1 -1
  21. package/_cjs/actions/solana/sendTransaction.js +32 -0
  22. package/_cjs/actions/solana/sendTransaction.js.map +1 -0
  23. package/_cjs/analytics.js +39 -3
  24. package/_cjs/analytics.js.map +1 -1
  25. package/_cjs/auth/utils/jwt.js +6 -5
  26. package/_cjs/auth/utils/jwt.js.map +1 -1
  27. package/_cjs/client/cdp.js +4 -1
  28. package/_cjs/client/cdp.js.map +1 -1
  29. package/_cjs/client/evm/evm.js +116 -10
  30. package/_cjs/client/evm/evm.js.map +1 -1
  31. package/_cjs/client/policies/policies.js +22 -0
  32. package/_cjs/client/policies/policies.js.map +1 -1
  33. package/_cjs/client/solana/solana.js +85 -8
  34. package/_cjs/client/solana/solana.js.map +1 -1
  35. package/_cjs/constants.js +1 -1
  36. package/_cjs/constants.js.map +1 -1
  37. package/_cjs/errors.js +19 -1
  38. package/_cjs/errors.js.map +1 -1
  39. package/_cjs/index.js +3 -1
  40. package/_cjs/index.js.map +1 -1
  41. package/_cjs/openapi-client/cdpApiClient.js +46 -6
  42. package/_cjs/openapi-client/cdpApiClient.js.map +1 -1
  43. package/_cjs/openapi-client/errors.js +38 -9
  44. package/_cjs/openapi-client/errors.js.map +1 -1
  45. package/_cjs/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.js +6 -1
  46. package/_cjs/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.js.map +1 -1
  47. package/_cjs/openapi-client/generated/solana-accounts/solana-accounts.js +34 -1
  48. package/_cjs/openapi-client/generated/solana-accounts/solana-accounts.js.map +1 -1
  49. package/_cjs/openapi-client/generated/solana-accounts/solana-accounts.msw.js +18 -1
  50. package/_cjs/openapi-client/generated/solana-accounts/solana-accounts.msw.js.map +1 -1
  51. package/_cjs/version.js +1 -1
  52. package/_esm/accounts/evm/networkToChainResolver.js +31 -0
  53. package/_esm/accounts/evm/networkToChainResolver.js.map +1 -0
  54. package/_esm/accounts/evm/resolveViemClients.js +4 -30
  55. package/_esm/accounts/evm/resolveViemClients.js.map +1 -1
  56. package/_esm/accounts/evm/toEvmServerAccount.js +84 -0
  57. package/_esm/accounts/evm/toEvmServerAccount.js.map +1 -1
  58. package/_esm/accounts/evm/toEvmSmartAccount.js +93 -0
  59. package/_esm/accounts/evm/toEvmSmartAccount.js.map +1 -1
  60. package/_esm/accounts/evm/toNetworkScopedEvmServerAccount.js +19 -7
  61. package/_esm/accounts/evm/toNetworkScopedEvmServerAccount.js.map +1 -1
  62. package/_esm/accounts/evm/toNetworkScopedEvmSmartAccount.js +65 -0
  63. package/_esm/accounts/evm/toNetworkScopedEvmSmartAccount.js.map +1 -1
  64. package/_esm/accounts/solana/toSolanaAccount.js +20 -0
  65. package/_esm/accounts/solana/toSolanaAccount.js.map +1 -1
  66. package/_esm/actions/evm/fund/fund.js +3 -2
  67. package/_esm/actions/evm/fund/fund.js.map +1 -1
  68. package/_esm/actions/evm/fund/quoteFund.js +3 -2
  69. package/_esm/actions/evm/fund/quoteFund.js.map +1 -1
  70. package/_esm/actions/solana/sendTransaction.js +29 -0
  71. package/_esm/actions/solana/sendTransaction.js.map +1 -0
  72. package/_esm/analytics.js +39 -3
  73. package/_esm/analytics.js.map +1 -1
  74. package/_esm/auth/utils/jwt.js +3 -2
  75. package/_esm/auth/utils/jwt.js.map +1 -1
  76. package/_esm/client/cdp.js +4 -1
  77. package/_esm/client/cdp.js.map +1 -1
  78. package/_esm/client/evm/evm.js +110 -4
  79. package/_esm/client/evm/evm.js.map +1 -1
  80. package/_esm/client/policies/policies.js +22 -0
  81. package/_esm/client/policies/policies.js.map +1 -1
  82. package/_esm/client/solana/solana.js +82 -5
  83. package/_esm/client/solana/solana.js.map +1 -1
  84. package/_esm/constants.js +1 -1
  85. package/_esm/constants.js.map +1 -1
  86. package/_esm/errors.js +17 -0
  87. package/_esm/errors.js.map +1 -1
  88. package/_esm/index.js +1 -0
  89. package/_esm/index.js.map +1 -1
  90. package/_esm/openapi-client/cdpApiClient.js +47 -7
  91. package/_esm/openapi-client/cdpApiClient.js.map +1 -1
  92. package/_esm/openapi-client/errors.js +36 -8
  93. package/_esm/openapi-client/errors.js.map +1 -1
  94. package/_esm/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.js +5 -0
  95. package/_esm/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.js.map +1 -1
  96. package/_esm/openapi-client/generated/solana-accounts/solana-accounts.js +32 -0
  97. package/_esm/openapi-client/generated/solana-accounts/solana-accounts.js.map +1 -1
  98. package/_esm/openapi-client/generated/solana-accounts/solana-accounts.msw.js +15 -0
  99. package/_esm/openapi-client/generated/solana-accounts/solana-accounts.msw.js.map +1 -1
  100. package/_esm/version.js +1 -1
  101. package/_types/accounts/evm/networkToChainResolver.d.ts +14 -0
  102. package/_types/accounts/evm/networkToChainResolver.d.ts.map +1 -0
  103. package/_types/accounts/evm/resolveViemClients.d.ts.map +1 -1
  104. package/_types/accounts/evm/toEvmServerAccount.d.ts.map +1 -1
  105. package/_types/accounts/evm/toEvmSmartAccount.d.ts.map +1 -1
  106. package/_types/accounts/evm/toNetworkScopedEvmServerAccount.d.ts.map +1 -1
  107. package/_types/accounts/evm/toNetworkScopedEvmSmartAccount.d.ts.map +1 -1
  108. package/_types/accounts/evm/types.d.ts +3 -3
  109. package/_types/accounts/evm/types.d.ts.map +1 -1
  110. package/_types/accounts/solana/toSolanaAccount.d.ts.map +1 -1
  111. package/_types/actions/evm/fund/fund.d.ts.map +1 -1
  112. package/_types/actions/evm/fund/quoteFund.d.ts.map +1 -1
  113. package/_types/actions/evm/types.d.ts +36 -3
  114. package/_types/actions/evm/types.d.ts.map +1 -1
  115. package/_types/actions/solana/sendTransaction.d.ts +23 -0
  116. package/_types/actions/solana/sendTransaction.d.ts.map +1 -0
  117. package/_types/analytics.d.ts +36 -1
  118. package/_types/analytics.d.ts.map +1 -1
  119. package/_types/auth/utils/jwt.d.ts.map +1 -1
  120. package/_types/client/cdp.d.ts.map +1 -1
  121. package/_types/client/evm/evm.d.ts +1 -0
  122. package/_types/client/evm/evm.d.ts.map +1 -1
  123. package/_types/client/policies/policies.d.ts.map +1 -1
  124. package/_types/client/solana/solana.d.ts +21 -2
  125. package/_types/client/solana/solana.d.ts.map +1 -1
  126. package/_types/client/solana/solana.types.d.ts +22 -3
  127. package/_types/client/solana/solana.types.d.ts.map +1 -1
  128. package/_types/constants.d.ts +1 -1
  129. package/_types/constants.d.ts.map +1 -1
  130. package/_types/errors.d.ts +11 -0
  131. package/_types/errors.d.ts.map +1 -1
  132. package/_types/index.d.ts +1 -0
  133. package/_types/index.d.ts.map +1 -1
  134. package/_types/openapi-client/cdpApiClient.d.ts.map +1 -1
  135. package/_types/openapi-client/errors.d.ts +46 -9
  136. package/_types/openapi-client/errors.d.ts.map +1 -1
  137. package/_types/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.d.ts +18 -0
  138. package/_types/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.d.ts.map +1 -1
  139. package/_types/openapi-client/generated/solana-accounts/solana-accounts.d.ts +27 -1
  140. package/_types/openapi-client/generated/solana-accounts/solana-accounts.d.ts.map +1 -1
  141. package/_types/openapi-client/generated/solana-accounts/solana-accounts.msw.d.ts +3 -1
  142. package/_types/openapi-client/generated/solana-accounts/solana-accounts.msw.d.ts.map +1 -1
  143. package/_types/openapi-client/index.d.ts +2 -0
  144. package/_types/openapi-client/index.d.ts.map +1 -1
  145. package/_types/version.d.ts +1 -1
  146. package/accounts/evm/networkToChainResolver.ts +34 -0
  147. package/accounts/evm/resolveViemClients.ts +6 -32
  148. package/accounts/evm/toEvmServerAccount.ts +98 -0
  149. package/accounts/evm/toEvmSmartAccount.ts +114 -2
  150. package/accounts/evm/toNetworkScopedEvmServerAccount.ts +22 -8
  151. package/accounts/evm/toNetworkScopedEvmSmartAccount.ts +75 -0
  152. package/accounts/evm/types.ts +3 -3
  153. package/accounts/solana/toSolanaAccount.ts +24 -0
  154. package/actions/evm/fund/fund.ts +3 -2
  155. package/actions/evm/fund/quoteFund.ts +3 -2
  156. package/actions/evm/types.ts +41 -2
  157. package/actions/solana/sendTransaction.ts +38 -0
  158. package/analytics.ts +73 -4
  159. package/auth/utils/jwt.ts +5 -2
  160. package/client/cdp.ts +7 -1
  161. package/client/evm/evm.ts +135 -6
  162. package/client/policies/policies.ts +28 -0
  163. package/client/solana/solana.ts +97 -5
  164. package/client/solana/solana.types.ts +25 -1
  165. package/constants.ts +1 -1
  166. package/errors.ts +18 -0
  167. package/index.ts +1 -0
  168. package/openapi-client/cdpApiClient.ts +87 -17
  169. package/openapi-client/errors.ts +65 -10
  170. package/openapi-client/generated/coinbaseDeveloperPlatformAPIs.schemas.ts +24 -0
  171. package/openapi-client/generated/solana-accounts/solana-accounts.msw.ts +32 -0
  172. package/openapi-client/generated/solana-accounts/solana-accounts.ts +43 -0
  173. package/package.json +1 -1
  174. package/version.ts +1 -1
@@ -13,19 +13,23 @@ import {
13
13
  ListTokenBalancesOptions,
14
14
  ListTokenBalancesResult,
15
15
  RequestFaucetOptions,
16
+ SendTransactionOptions,
16
17
  SignatureResult,
17
18
  SignMessageOptions,
18
19
  SignTransactionOptions,
19
20
  SolanaClientInterface,
21
+ TransactionResult,
20
22
  UpdateSolanaAccountOptions,
21
23
  } from "./solana.types.js";
22
24
  import { toSolanaAccount } from "../../accounts/solana/toSolanaAccount.js";
23
25
  import { SolanaAccount } from "../../accounts/solana/types.js";
24
26
  import { requestFaucet } from "../../actions/solana/requestFaucet.js";
27
+ import { sendTransaction } from "../../actions/solana/sendTransaction.js";
25
28
  import { signMessage } from "../../actions/solana/signMessage.js";
26
29
  import { signTransaction } from "../../actions/solana/signTransaction.js";
27
30
  import { Analytics } from "../../analytics.js";
28
31
  import { ImportAccountPublicRSAKey } from "../../constants.js";
32
+ import { UserInputValidationError } from "../../errors.js";
29
33
  import { APIError } from "../../openapi-client/errors.js";
30
34
  import { CdpOpenApiClient } from "../../openapi-client/index.js";
31
35
  import {
@@ -69,6 +73,11 @@ export class SolanaClient implements SolanaClientInterface {
69
73
  * ```
70
74
  */
71
75
  async createAccount(options: CreateAccountOptions = {}): Promise<SolanaAccount> {
76
+ Analytics.trackAction({
77
+ action: "create_account",
78
+ accountType: "solana",
79
+ });
80
+
72
81
  const openApiAccount = await CdpOpenApiClient.createSolanaAccount(
73
82
  {
74
83
  name: options.name,
@@ -111,6 +120,11 @@ export class SolanaClient implements SolanaClientInterface {
111
120
  * ```
112
121
  */
113
122
  async exportAccount(options: ExportAccountOptions): Promise<string> {
123
+ Analytics.trackAction({
124
+ action: "export_account",
125
+ accountType: "solana",
126
+ });
127
+
114
128
  const { publicKey, privateKey } = await generateExportEncryptionKeyPair();
115
129
 
116
130
  const { encryptedPrivateKey } = await (async () => {
@@ -134,7 +148,7 @@ export class SolanaClient implements SolanaClientInterface {
134
148
  );
135
149
  }
136
150
 
137
- throw new Error("Either address or name must be provided");
151
+ throw new UserInputValidationError("Either address or name must be provided");
138
152
  })();
139
153
 
140
154
  const decryptedPrivateKey = decryptWithPrivateKey(privateKey, encryptedPrivateKey);
@@ -180,6 +194,11 @@ export class SolanaClient implements SolanaClientInterface {
180
194
  * ```
181
195
  */
182
196
  async importAccount(options: ImportAccountOptions): Promise<SolanaAccount> {
197
+ Analytics.trackAction({
198
+ action: "import_account",
199
+ accountType: "solana",
200
+ });
201
+
183
202
  let privateKeyBytes: Uint8Array = new Uint8Array();
184
203
 
185
204
  if (typeof options.privateKey === "string") {
@@ -189,7 +208,7 @@ export class SolanaClient implements SolanaClientInterface {
189
208
  }
190
209
 
191
210
  if (privateKeyBytes.length !== 32 && privateKeyBytes.length !== 64) {
192
- throw new Error("Invalid private key length");
211
+ throw new UserInputValidationError("Invalid private key length");
193
212
  }
194
213
 
195
214
  if (privateKeyBytes.length === 64) {
@@ -250,6 +269,11 @@ export class SolanaClient implements SolanaClientInterface {
250
269
  * ```
251
270
  */
252
271
  async getAccount(options: GetAccountOptions): Promise<SolanaAccount> {
272
+ Analytics.trackAction({
273
+ action: "get_account",
274
+ accountType: "solana",
275
+ });
276
+
253
277
  const openApiAccount = await (() => {
254
278
  if (options.address) {
255
279
  return CdpOpenApiClient.getSolanaAccount(options.address);
@@ -259,7 +283,7 @@ export class SolanaClient implements SolanaClientInterface {
259
283
  return CdpOpenApiClient.getSolanaAccountByName(options.name);
260
284
  }
261
285
 
262
- throw new Error("Either address or name must be provided");
286
+ throw new UserInputValidationError("Either address or name must be provided");
263
287
  })();
264
288
 
265
289
  const account = toSolanaAccount(CdpOpenApiClient, {
@@ -287,6 +311,11 @@ export class SolanaClient implements SolanaClientInterface {
287
311
  * ```
288
312
  */
289
313
  async getOrCreateAccount(options: GetOrCreateAccountOptions): Promise<SolanaAccount> {
314
+ Analytics.trackAction({
315
+ action: "get_or_create_account",
316
+ accountType: "solana",
317
+ });
318
+
290
319
  try {
291
320
  const account = await this.getAccount(options);
292
321
  return account;
@@ -341,6 +370,11 @@ export class SolanaClient implements SolanaClientInterface {
341
370
  * ```
342
371
  */
343
372
  async listAccounts(options: ListAccountsOptions = {}): Promise<ListAccountsResult> {
373
+ Analytics.trackAction({
374
+ action: "list_accounts",
375
+ accountType: "solana",
376
+ });
377
+
344
378
  const solAccounts = await CdpOpenApiClient.listSolanaAccounts({
345
379
  pageSize: options.pageSize,
346
380
  pageToken: options.pageToken,
@@ -379,6 +413,11 @@ export class SolanaClient implements SolanaClientInterface {
379
413
  * ```
380
414
  */
381
415
  async requestFaucet(options: RequestFaucetOptions): Promise<SignatureResult> {
416
+ Analytics.trackAction({
417
+ action: "request_faucet",
418
+ accountType: "solana",
419
+ });
420
+
382
421
  return requestFaucet(CdpOpenApiClient, options);
383
422
  }
384
423
 
@@ -405,6 +444,11 @@ export class SolanaClient implements SolanaClientInterface {
405
444
  * ```
406
445
  */
407
446
  async signMessage(options: SignMessageOptions): Promise<SignatureResult> {
447
+ Analytics.trackAction({
448
+ action: "sign_message",
449
+ accountType: "solana",
450
+ });
451
+
408
452
  return signMessage(CdpOpenApiClient, options);
409
453
  }
410
454
 
@@ -442,6 +486,11 @@ export class SolanaClient implements SolanaClientInterface {
442
486
  * ```
443
487
  */
444
488
  async signTransaction(options: SignTransactionOptions): Promise<SignatureResult> {
489
+ Analytics.trackAction({
490
+ action: "sign_transaction",
491
+ accountType: "solana",
492
+ });
493
+
445
494
  return signTransaction(CdpOpenApiClient, options);
446
495
  }
447
496
 
@@ -487,6 +536,11 @@ export class SolanaClient implements SolanaClientInterface {
487
536
  * ```
488
537
  */
489
538
  async updateAccount(options: UpdateSolanaAccountOptions): Promise<SolanaAccount> {
539
+ Analytics.trackAction({
540
+ action: "update_account",
541
+ accountType: "solana",
542
+ });
543
+
490
544
  const openApiAccount = await CdpOpenApiClient.updateSolanaAccount(
491
545
  options.address,
492
546
  options.update,
@@ -502,12 +556,42 @@ export class SolanaClient implements SolanaClientInterface {
502
556
  return account;
503
557
  }
504
558
 
559
+ /**
560
+ * Sends a Solana transaction using the Coinbase API.
561
+ *
562
+ * @param {SendTransactionOptions} options - Parameters for sending the Solana transaction.
563
+ * @param {string} options.network - The network to send the transaction to.
564
+ * @param {string} options.transaction - The base64 encoded transaction to send.
565
+ * @param {string} [options.idempotencyKey] - An idempotency key.
566
+ *
567
+ * @returns A promise that resolves to the transaction result.
568
+ *
569
+ * @example
570
+ * ```ts
571
+ * const signature = await cdp.solana.sendTransaction({
572
+ * network: "solana-devnet",
573
+ * transaction: "...",
574
+ * });
575
+ * ```
576
+ */
577
+ async sendTransaction(options: SendTransactionOptions): Promise<TransactionResult> {
578
+ Analytics.trackAction({
579
+ action: "send_transaction",
580
+ accountType: "solana",
581
+ properties: {
582
+ network: options.network,
583
+ },
584
+ });
585
+
586
+ return sendTransaction(CdpOpenApiClient, options);
587
+ }
588
+
505
589
  /**
506
590
  * Lists the token balances for a Solana account.
507
591
  *
508
592
  * @param {ListTokenBalancesOptions} options - Parameters for listing the Solana token balances.
509
593
  * @param {string} options.address - The address of the account to list token balances for.
510
- * @param {string} options.network - The network to list token balances for.
594
+ * @param {string} [options.network] - The network to list token balances for. Defaults to "solana".
511
595
  * @param {number} [options.pageSize] - The number of token balances to return.
512
596
  * @param {string} [options.pageToken] - The page token to begin listing from.
513
597
  * This is obtained by previous calls to this method.
@@ -520,8 +604,16 @@ export class SolanaClient implements SolanaClientInterface {
520
604
  * ```
521
605
  */
522
606
  async listTokenBalances(options: ListTokenBalancesOptions): Promise<ListTokenBalancesResult> {
607
+ Analytics.trackAction({
608
+ action: "list_token_balances",
609
+ accountType: "solana",
610
+ properties: {
611
+ network: options.network,
612
+ },
613
+ });
614
+
523
615
  const tokenBalances = await CdpOpenApiClient.listSolanaTokenBalances(
524
- options.network,
616
+ options.network || "solana",
525
617
  options.address,
526
618
  {
527
619
  pageSize: options.pageSize,
@@ -2,6 +2,8 @@ import { Account, SolanaAccount } from "../../accounts/solana/types.js";
2
2
  import {
3
3
  ListSolanaTokenBalancesNetwork,
4
4
  OpenApiSolanaMethods,
5
+ SendSolanaTransactionBody,
6
+ SendSolanaTransactionBodyNetwork,
5
7
  UpdateSolanaAccountBody,
6
8
  } from "../../openapi-client/index.js";
7
9
 
@@ -23,6 +25,7 @@ export type SolanaClientInterface = Omit<
23
25
  | "exportSolanaAccountByName" // mapped to exportAccount
24
26
  | "importSolanaAccount" // mapped to importAccount
25
27
  | "listSolanaTokenBalances" // mapped to listTokenBalances
28
+ | "sendSolanaTransaction" // mapped to sendTransaction
26
29
  > & {
27
30
  createAccount: (options: CreateAccountOptions) => Promise<Account>;
28
31
  exportAccount: (options: ExportAccountOptions) => Promise<string>;
@@ -35,6 +38,7 @@ export type SolanaClientInterface = Omit<
35
38
  signMessage: (options: SignMessageOptions) => Promise<SignatureResult>;
36
39
  signTransaction: (options: SignTransactionOptions) => Promise<SignatureResult>;
37
40
  listTokenBalances: (options: ListTokenBalancesOptions) => Promise<ListTokenBalancesResult>;
41
+ sendTransaction: (options: SendSolanaTransactionBody) => Promise<SignatureResult>;
38
42
  };
39
43
 
40
44
  /**
@@ -178,13 +182,33 @@ export interface ListTokenBalancesOptions {
178
182
  /** The address of the account. */
179
183
  address: string;
180
184
  /** The network to list token balances for. */
181
- network: ListSolanaTokenBalancesNetwork;
185
+ network?: ListSolanaTokenBalancesNetwork;
182
186
  /** The page size. */
183
187
  pageSize?: number;
184
188
  /** The page token. */
185
189
  pageToken?: string;
186
190
  }
187
191
 
192
+ /**
193
+ * Options for sending a Solana transaction.
194
+ */
195
+ export interface SendTransactionOptions {
196
+ /** The network to send the transaction to. */
197
+ network: SendSolanaTransactionBodyNetwork;
198
+ /** The base64 encoded transaction to send. */
199
+ transaction: string;
200
+ /** The idempotency key. */
201
+ idempotencyKey?: string;
202
+ }
203
+
204
+ /**
205
+ * The result of sending a Solana transaction.
206
+ */
207
+ export interface TransactionResult {
208
+ /** The signature of the transaction base58 encoded. */
209
+ signature: string;
210
+ }
211
+
188
212
  export interface SolanaTokenAmount {
189
213
  /** The amount of the token. */
190
214
  amount: bigint;
package/constants.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The URL to the Coinbase CDP API error docs page.
3
3
  */
4
- export const ERROR_DOCS_PAGE_URL = "https://docs.cdp.coinbase.com/api-v2/docs/errors";
4
+ export const ERROR_DOCS_PAGE_URL = "https://docs.cdp.coinbase.com/api-reference/v2/errors";
5
5
 
6
6
  /**
7
7
  * The public RSA key used to encrypt the private key when importing an EVM or Solana account.
package/errors.ts CHANGED
@@ -15,3 +15,21 @@ export class TimeoutError extends Error {
15
15
  }
16
16
  }
17
17
  }
18
+
19
+ /**
20
+ * UserInputValidationError is thrown when validation of a user-supplied input fails.
21
+ */
22
+ export class UserInputValidationError extends Error {
23
+ /**
24
+ * Initializes a new UserInputValidationError instance.
25
+ *
26
+ * @param message - The user input validation error message.
27
+ */
28
+ constructor(message: string) {
29
+ super(message);
30
+ this.name = "UserInputValidationError";
31
+ if (Error.captureStackTrace) {
32
+ Error.captureStackTrace(this, UserInputValidationError);
33
+ }
34
+ }
35
+ }
package/index.ts CHANGED
@@ -7,5 +7,6 @@ export {
7
7
  type CreatePolicyBody,
8
8
  type UpdatePolicyBody,
9
9
  } from "./policies/schema.js";
10
+ export { NetworkError } from "./openapi-client/errors.js";
10
11
 
11
12
  export { parseEther } from "viem";
@@ -3,13 +3,7 @@ import Axios, { AxiosInstance, AxiosRequestConfig } from "axios";
3
3
 
4
4
  import { withAuth } from "../auth/hooks/axios/index.js";
5
5
  import { ERROR_DOCS_PAGE_URL } from "../constants.js";
6
- import {
7
- isOpenAPIError,
8
- APIError,
9
- HttpErrorType,
10
- UnknownApiError,
11
- UnknownError,
12
- } from "./errors.js";
6
+ import { isOpenAPIError, APIError, UnknownError, NetworkError } from "./errors.js";
13
7
 
14
8
  import type { Prettify } from "../types/utils.js";
15
9
 
@@ -135,11 +129,52 @@ export const cdpApiClient = async <T>(
135
129
  } catch (error) {
136
130
  // eslint-disable-next-line import/no-named-as-default-member
137
131
  if (Axios.isAxiosError(error) && !error.response) {
138
- throw new UnknownApiError(
139
- HttpErrorType.unknown,
140
- error.cause instanceof Error ? error.cause.message : error.message,
141
- error.cause,
142
- );
132
+ // Network-level errors (no response received)
133
+ const errorMessage = (error.message || "").toLowerCase();
134
+ const errorCode = error.code?.toLowerCase();
135
+
136
+ // Categorize network errors based on error messages and codes
137
+ if (errorCode === "econnrefused" || errorMessage.includes("connection refused")) {
138
+ throw new NetworkError(
139
+ "network_connection_failed",
140
+ "Unable to connect to CDP service. The service may be unavailable.",
141
+ { code: error.code, message: error.message, retryable: true },
142
+ error.cause,
143
+ );
144
+ } else if (
145
+ errorCode === "etimedout" ||
146
+ errorCode === "econnaborted" ||
147
+ errorMessage.includes("timeout")
148
+ ) {
149
+ throw new NetworkError(
150
+ "network_timeout",
151
+ "Request timed out. Please try again.",
152
+ { code: error.code, message: error.message, retryable: true },
153
+ error.cause,
154
+ );
155
+ } else if (errorCode === "enotfound" || errorMessage.includes("getaddrinfo")) {
156
+ throw new NetworkError(
157
+ "network_dns_failure",
158
+ "DNS resolution failed. Please check your network connection.",
159
+ { code: error.code, message: error.message, retryable: false },
160
+ error.cause,
161
+ );
162
+ } else if (errorMessage.includes("network error") || errorMessage.includes("econnreset")) {
163
+ throw new NetworkError(
164
+ "network_connection_failed",
165
+ "Network error occurred. Please check your connection and try again.",
166
+ { code: error.code, message: error.message, retryable: true },
167
+ error.cause,
168
+ );
169
+ } else {
170
+ // Generic network error
171
+ throw new NetworkError(
172
+ "unknown",
173
+ error.cause instanceof Error ? error.cause.message : error.message,
174
+ { code: error.code, message: error.message, retryable: true },
175
+ error.cause,
176
+ );
177
+ }
143
178
  }
144
179
 
145
180
  // eslint-disable-next-line import/no-named-as-default-member
@@ -155,20 +190,55 @@ export const cdpApiClient = async <T>(
155
190
  );
156
191
  } else {
157
192
  const statusCode = error.response.status;
193
+ const responseData = error.response.data;
194
+
195
+ // Check for gateway-level errors that might indicate network issues
196
+ const isGatewayError =
197
+ responseData &&
198
+ typeof responseData === "string" &&
199
+ (responseData.toLowerCase().includes("forbidden") ||
200
+ responseData.toLowerCase().includes("ip") ||
201
+ responseData.toLowerCase().includes("blocked") ||
202
+ responseData.toLowerCase().includes("gateway"));
203
+
158
204
  switch (statusCode) {
159
205
  case 401:
160
206
  throw new APIError(
161
207
  statusCode,
162
- HttpErrorType.unauthorized,
208
+ "unauthorized",
163
209
  "Unauthorized.",
164
210
  undefined,
165
211
  `${ERROR_DOCS_PAGE_URL}#unauthorized`,
166
212
  error.cause,
167
213
  );
214
+ case 403:
215
+ // Special handling for IP blocklist and other gateway-level 403s
216
+ if (isGatewayError) {
217
+ throw new NetworkError(
218
+ "network_ip_blocked",
219
+ "Access denied. Your IP address may be blocked or restricted.",
220
+ {
221
+ code: "IP_BLOCKED",
222
+ message:
223
+ typeof responseData === "string" ? responseData : JSON.stringify(responseData),
224
+ retryable: false,
225
+ },
226
+ error.cause,
227
+ );
228
+ }
229
+ // Regular 403 forbidden error
230
+ throw new APIError(
231
+ statusCode,
232
+ "unauthorized",
233
+ "Forbidden. You don't have permission to access this resource.",
234
+ undefined,
235
+ `${ERROR_DOCS_PAGE_URL}#forbidden`,
236
+ error.cause,
237
+ );
168
238
  case 404:
169
239
  throw new APIError(
170
240
  statusCode,
171
- HttpErrorType.not_found,
241
+ "not_found",
172
242
  "API not found.",
173
243
  undefined,
174
244
  `${ERROR_DOCS_PAGE_URL}#not_found`,
@@ -177,7 +247,7 @@ export const cdpApiClient = async <T>(
177
247
  case 502:
178
248
  throw new APIError(
179
249
  statusCode,
180
- HttpErrorType.bad_gateway,
250
+ "bad_gateway",
181
251
  "Bad gateway.",
182
252
  undefined,
183
253
  `${ERROR_DOCS_PAGE_URL}`,
@@ -186,7 +256,7 @@ export const cdpApiClient = async <T>(
186
256
  case 503:
187
257
  throw new APIError(
188
258
  statusCode,
189
- HttpErrorType.service_unavailable,
259
+ "service_unavailable",
190
260
  "Service unavailable. Please try again later.",
191
261
  undefined,
192
262
  `${ERROR_DOCS_PAGE_URL}`,
@@ -209,7 +279,7 @@ export const cdpApiClient = async <T>(
209
279
 
210
280
  throw new APIError(
211
281
  statusCode,
212
- HttpErrorType.unexpected_error,
282
+ "unexpected_error",
213
283
  errorMessage,
214
284
  undefined,
215
285
  `${ERROR_DOCS_PAGE_URL}`,
@@ -1,18 +1,20 @@
1
+ import { ERROR_DOCS_PAGE_URL } from "../constants.js";
1
2
  import {
2
3
  Error as OpenAPIError,
3
4
  ErrorType as OpenAPIErrorType,
4
5
  } from "./generated/coinbaseDeveloperPlatformAPIs.schemas.js";
5
6
 
6
- export const HttpErrorType = {
7
- unexpected_error: "unexpected_error",
8
- unauthorized: "unauthorized",
9
- not_found: "not_found",
10
- bad_gateway: "bad_gateway",
11
- service_unavailable: "service_unavailable",
12
- unknown: "unknown",
13
- } as const;
14
-
15
- export type HttpErrorType = (typeof HttpErrorType)[keyof typeof HttpErrorType];
7
+ export type HttpErrorType =
8
+ | "unexpected_error"
9
+ | "unauthorized"
10
+ | "not_found"
11
+ | "bad_gateway"
12
+ | "service_unavailable"
13
+ | "unknown"
14
+ | "network_timeout"
15
+ | "network_connection_failed"
16
+ | "network_ip_blocked"
17
+ | "network_dns_failure";
16
18
 
17
19
  /**
18
20
  * Extended error codes that include both OpenAPI errors and network errors
@@ -114,6 +116,59 @@ export class UnknownError extends Error {
114
116
  }
115
117
  }
116
118
 
119
+ /**
120
+ * Error thrown when a network-level failure occurs before reaching the CDP service
121
+ * This includes gateway errors, IP blocklist rejections, DNS failures, etc.
122
+ */
123
+ export class NetworkError extends APIError {
124
+ networkDetails?: {
125
+ code?: string;
126
+ message?: string;
127
+ retryable?: boolean;
128
+ };
129
+
130
+ /**
131
+ * Constructor for the NetworkError class
132
+ *
133
+ * @param errorType - The type of network error
134
+ * @param errorMessage - The error message
135
+ * @param networkDetails - Additional network error details
136
+ * @param networkDetails.code - The error code
137
+ * @param networkDetails.message - The error message
138
+ * @param networkDetails.retryable - Whether the error is retryable
139
+ * @param cause - The cause of the error
140
+ */
141
+ constructor(
142
+ errorType: HttpErrorType,
143
+ errorMessage: string,
144
+ networkDetails?: { code?: string; message?: string; retryable?: boolean },
145
+ cause?: Error,
146
+ ) {
147
+ super(
148
+ 0, // Status code 0 indicates no response was received
149
+ errorType,
150
+ errorMessage,
151
+ undefined,
152
+ `${ERROR_DOCS_PAGE_URL}#network-errors`,
153
+ cause,
154
+ );
155
+ this.name = "NetworkError";
156
+ this.networkDetails = networkDetails;
157
+ }
158
+
159
+ /**
160
+ * Convert the error to a JSON object, including network details
161
+ *
162
+ * @returns The error as a JSON object
163
+ */
164
+ toJSON() {
165
+ return {
166
+ ...super.toJSON(),
167
+ ...(this.networkDetails && { networkDetails: this.networkDetails }),
168
+ };
169
+ }
170
+ }
171
+
117
172
  /**
118
173
  * Type guard to check if an object is an OpenAPIError
119
174
  *
@@ -2252,6 +2252,30 @@ export type SignSolanaMessage200 = {
2252
2252
  signature: string;
2253
2253
  };
2254
2254
 
2255
+ /**
2256
+ * The Solana network to send the transaction to.
2257
+ */
2258
+ export type SendSolanaTransactionBodyNetwork =
2259
+ (typeof SendSolanaTransactionBodyNetwork)[keyof typeof SendSolanaTransactionBodyNetwork];
2260
+
2261
+ // eslint-disable-next-line @typescript-eslint/no-redeclare
2262
+ export const SendSolanaTransactionBodyNetwork = {
2263
+ solana: "solana",
2264
+ "solana-devnet": "solana-devnet",
2265
+ } as const;
2266
+
2267
+ export type SendSolanaTransactionBody = {
2268
+ /** The Solana network to send the transaction to. */
2269
+ network: SendSolanaTransactionBodyNetwork;
2270
+ /** The base64 encoded transaction to sign and send. This transaction can contain multiple instructions for native Solana batching. */
2271
+ transaction: string;
2272
+ };
2273
+
2274
+ export type SendSolanaTransaction200 = {
2275
+ /** The base58 encoded transaction signature. */
2276
+ transactionSignature: string;
2277
+ };
2278
+
2255
2279
  /**
2256
2280
  * The token to request funds for.
2257
2281
  */
@@ -13,6 +13,7 @@ import type {
13
13
  ExportSolanaAccount200,
14
14
  ExportSolanaAccountByName200,
15
15
  ListSolanaAccounts200,
16
+ SendSolanaTransaction200,
16
17
  SignSolanaMessage200,
17
18
  SignSolanaTransaction200,
18
19
  SolanaAccount,
@@ -203,6 +204,13 @@ export const getSignSolanaMessageResponseMock = (
203
204
  overrideResponse: Partial<SignSolanaMessage200> = {},
204
205
  ): SignSolanaMessage200 => ({ signature: faker.string.alpha(20), ...overrideResponse });
205
206
 
207
+ export const getSendSolanaTransactionResponseMock = (
208
+ overrideResponse: Partial<SendSolanaTransaction200> = {},
209
+ ): SendSolanaTransaction200 => ({
210
+ transactionSignature: faker.string.alpha(20),
211
+ ...overrideResponse,
212
+ });
213
+
206
214
  export const getListSolanaAccountsMockHandler = (
207
215
  overrideResponse?:
208
216
  | ListSolanaAccounts200
@@ -432,6 +440,29 @@ export const getSignSolanaMessageMockHandler = (
432
440
  );
433
441
  });
434
442
  };
443
+
444
+ export const getSendSolanaTransactionMockHandler = (
445
+ overrideResponse?:
446
+ | SendSolanaTransaction200
447
+ | ((
448
+ info: Parameters<Parameters<typeof http.post>[1]>[0],
449
+ ) => Promise<SendSolanaTransaction200> | SendSolanaTransaction200),
450
+ ) => {
451
+ return http.post("*/v2/solana/accounts/send/transaction", async info => {
452
+ await delay(0);
453
+
454
+ return new HttpResponse(
455
+ JSON.stringify(
456
+ overrideResponse !== undefined
457
+ ? typeof overrideResponse === "function"
458
+ ? await overrideResponse(info)
459
+ : overrideResponse
460
+ : getSendSolanaTransactionResponseMock(),
461
+ ),
462
+ { status: 200, headers: { "Content-Type": "application/json" } },
463
+ );
464
+ });
465
+ };
435
466
  export const getSolanaAccountsMock = () => [
436
467
  getListSolanaAccountsMockHandler(),
437
468
  getCreateSolanaAccountMockHandler(),
@@ -443,4 +474,5 @@ export const getSolanaAccountsMock = () => [
443
474
  getExportSolanaAccountByNameMockHandler(),
444
475
  getSignSolanaTransactionMockHandler(),
445
476
  getSignSolanaMessageMockHandler(),
477
+ getSendSolanaTransactionMockHandler(),
446
478
  ];