@across-protocol/sdk 4.1.15 → 4.1.17

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 (79) hide show
  1. package/dist/cjs/providers/cachedProvider.d.ts +2 -0
  2. package/dist/cjs/providers/cachedProvider.js +37 -15
  3. package/dist/cjs/providers/cachedProvider.js.map +1 -1
  4. package/dist/cjs/providers/index.d.ts +1 -0
  5. package/dist/cjs/providers/index.js +1 -0
  6. package/dist/cjs/providers/index.js.map +1 -1
  7. package/dist/cjs/providers/retryProvider.js +3 -3
  8. package/dist/cjs/providers/retryProvider.js.map +1 -1
  9. package/dist/cjs/providers/solana/baseRpcFactories.d.ts +11 -0
  10. package/dist/cjs/providers/solana/baseRpcFactories.js +30 -0
  11. package/dist/cjs/providers/solana/baseRpcFactories.js.map +1 -0
  12. package/dist/cjs/providers/solana/defaultRpcFactory.d.ts +6 -0
  13. package/dist/cjs/providers/solana/defaultRpcFactory.js +22 -0
  14. package/dist/cjs/providers/solana/defaultRpcFactory.js.map +1 -0
  15. package/dist/cjs/providers/solana/index.d.ts +4 -0
  16. package/dist/cjs/providers/solana/index.js +8 -0
  17. package/dist/cjs/providers/solana/index.js.map +1 -0
  18. package/dist/cjs/providers/solana/rateLimitedRpcFactory.d.ts +16 -0
  19. package/dist/cjs/providers/solana/rateLimitedRpcFactory.js +105 -0
  20. package/dist/cjs/providers/solana/rateLimitedRpcFactory.js.map +1 -0
  21. package/dist/cjs/providers/solana/utils.d.ts +6 -0
  22. package/dist/cjs/providers/solana/utils.js +3 -0
  23. package/dist/cjs/providers/solana/utils.js.map +1 -0
  24. package/dist/cjs/providers/utils.d.ts +2 -1
  25. package/dist/cjs/providers/utils.js +1 -0
  26. package/dist/cjs/providers/utils.js.map +1 -1
  27. package/dist/esm/providers/cachedProvider.d.ts +2 -0
  28. package/dist/esm/providers/cachedProvider.js +39 -16
  29. package/dist/esm/providers/cachedProvider.js.map +1 -1
  30. package/dist/esm/providers/index.d.ts +1 -0
  31. package/dist/esm/providers/index.js +1 -0
  32. package/dist/esm/providers/index.js.map +1 -1
  33. package/dist/esm/providers/retryProvider.js +4 -3
  34. package/dist/esm/providers/retryProvider.js.map +1 -1
  35. package/dist/esm/providers/solana/baseRpcFactories.d.ts +11 -0
  36. package/dist/esm/providers/solana/baseRpcFactories.js +31 -0
  37. package/dist/esm/providers/solana/baseRpcFactories.js.map +1 -0
  38. package/dist/esm/providers/solana/defaultRpcFactory.d.ts +6 -0
  39. package/dist/esm/providers/solana/defaultRpcFactory.js +20 -0
  40. package/dist/esm/providers/solana/defaultRpcFactory.js.map +1 -0
  41. package/dist/esm/providers/solana/index.d.ts +4 -0
  42. package/dist/esm/providers/solana/index.js +5 -0
  43. package/dist/esm/providers/solana/index.js.map +1 -0
  44. package/dist/esm/providers/solana/rateLimitedRpcFactory.d.ts +16 -0
  45. package/dist/esm/providers/solana/rateLimitedRpcFactory.js +116 -0
  46. package/dist/esm/providers/solana/rateLimitedRpcFactory.js.map +1 -0
  47. package/dist/esm/providers/solana/utils.d.ts +9 -0
  48. package/dist/esm/providers/solana/utils.js +2 -0
  49. package/dist/esm/providers/solana/utils.js.map +1 -0
  50. package/dist/esm/providers/utils.d.ts +2 -1
  51. package/dist/esm/providers/utils.js +1 -0
  52. package/dist/esm/providers/utils.js.map +1 -1
  53. package/dist/types/providers/cachedProvider.d.ts +2 -0
  54. package/dist/types/providers/cachedProvider.d.ts.map +1 -1
  55. package/dist/types/providers/index.d.ts +1 -0
  56. package/dist/types/providers/index.d.ts.map +1 -1
  57. package/dist/types/providers/retryProvider.d.ts.map +1 -1
  58. package/dist/types/providers/solana/baseRpcFactories.d.ts +12 -0
  59. package/dist/types/providers/solana/baseRpcFactories.d.ts.map +1 -0
  60. package/dist/types/providers/solana/defaultRpcFactory.d.ts +7 -0
  61. package/dist/types/providers/solana/defaultRpcFactory.d.ts.map +1 -0
  62. package/dist/types/providers/solana/index.d.ts +5 -0
  63. package/dist/types/providers/solana/index.d.ts.map +1 -0
  64. package/dist/types/providers/solana/rateLimitedRpcFactory.d.ts +17 -0
  65. package/dist/types/providers/solana/rateLimitedRpcFactory.d.ts.map +1 -0
  66. package/dist/types/providers/solana/utils.d.ts +10 -0
  67. package/dist/types/providers/solana/utils.d.ts.map +1 -0
  68. package/dist/types/providers/utils.d.ts +2 -1
  69. package/dist/types/providers/utils.d.ts.map +1 -1
  70. package/package.json +3 -1
  71. package/src/providers/cachedProvider.ts +23 -2
  72. package/src/providers/index.ts +1 -0
  73. package/src/providers/retryProvider.ts +4 -3
  74. package/src/providers/solana/baseRpcFactories.ts +25 -0
  75. package/src/providers/solana/defaultRpcFactory.ts +13 -0
  76. package/src/providers/solana/index.ts +4 -0
  77. package/src/providers/solana/rateLimitedRpcFactory.ts +101 -0
  78. package/src/providers/solana/utils.ts +14 -0
  79. package/src/providers/utils.ts +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultRpcFactory.d.ts","sourceRoot":"","sources":["../../../../src/providers/solana/defaultRpcFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D,qBAAa,uBAAwB,SAAQ,uBAAuB;gBACtD,GAAG,wBAAwB,EAAE,qBAAqB,CAAC,OAAO,uBAAuB,CAAC;IAIvF,eAAe,IAAI,YAAY;CAGvC"}
@@ -0,0 +1,5 @@
1
+ export * from "./baseRpcFactories";
2
+ export * from "./defaultRpcFactory";
3
+ export * from "./rateLimitedRpcFactory";
4
+ export * from "./utils";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/providers/solana/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { RpcResponse } from "@solana/web3.js";
2
+ import { Logger } from "winston";
3
+ import { SolanaClusterRpcFactory } from "./baseRpcFactories";
4
+ import { SolanaDefaultRpcFactory } from "./defaultRpcFactory";
5
+ export declare class RateLimitedSolanaRpcFactory extends SolanaClusterRpcFactory {
6
+ readonly pctRpcCallsLogged: number;
7
+ readonly logger: Logger;
8
+ private queue;
9
+ private readonly defaultTransport;
10
+ constructor(maxConcurrency: number, pctRpcCallsLogged: number, logger: Logger, ...defaultConstructorParams: ConstructorParameters<typeof SolanaDefaultRpcFactory>);
11
+ private wrapSendWithLog;
12
+ createTransport(): <TResponse>(config: Readonly<{
13
+ payload: unknown;
14
+ signal?: AbortSignal | undefined;
15
+ }>) => Promise<TResponse>;
16
+ }
17
+ //# sourceMappingURL=rateLimitedRpcFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimitedRpcFactory.d.ts","sourceRoot":"","sources":["../../../../src/providers/solana/rateLimitedRpcFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAgB,MAAM,iBAAiB,CAAC;AAE5D,OAAgB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAM9D,qBAAa,2BAA4B,SAAQ,uBAAuB;IAWpE,QAAQ,CAAC,iBAAiB,EAAE,MAAM;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAVzB,OAAO,CAAC,KAAK,CAAmC;IAGhD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAe;gBAK9C,cAAc,EAAE,MAAM,EACb,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,MAEf,EACF,GAAG,wBAAwB,EAAE,qBAAqB,CAAC,OAAO,uBAAuB,CAAC;YAkBtE,eAAe;IA0CtB,eAAe;;;;CAevB"}
@@ -0,0 +1,10 @@
1
+ import { RpcTransport } from "@solana/rpc-spec";
2
+ /**
3
+ * This is the type we pass to define a Solana RPC request "task".
4
+ */
5
+ export interface SolanaRateLimitTask {
6
+ rpcArgs: Parameters<RpcTransport>;
7
+ resolve: (result: unknown) => void;
8
+ reject: (err: unknown) => void;
9
+ }
10
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/providers/solana/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAElC,OAAO,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;IAIlC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAChC"}
@@ -51,6 +51,7 @@ export declare function compareRpcResults(method: string, rpcResultA: unknown, r
51
51
  export declare enum CacheType {
52
52
  NONE = 0,
53
53
  WITH_TTL = 1,
54
- NO_TTL = 2
54
+ NO_TTL = 2,
55
+ DECIDE_TTL_POST_SEND = 3
55
56
  }
56
57
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/providers/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAepD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,IAAI,WAAW,CAE7E;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,YAAsB,GAChC,MAAM,CAIR;AAmBD,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAOT;AAED,wBAAgB,kCAAkC,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAOnH;AAyBD;;GAEG;AACH,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAInC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,qBAAqB,EAAE,YAAY,EAAE,MAAM,UAElG;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;EAG7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAkBnG;AAED,oBAAY,SAAS;IACnB,IAAI,IAAA;IACJ,QAAQ,IAAA;IACR,MAAM,IAAA;CACP"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/providers/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAepD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,IAAI,WAAW,CAE7E;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,YAAsB,GAChC,MAAM,CAIR;AAmBD,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAOT;AAED,wBAAgB,kCAAkC,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAOnH;AAyBD;;GAEG;AACH,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAInC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,qBAAqB,EAAE,YAAY,EAAE,MAAM,UAElG;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;EAG7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAkBnG;AAED,oBAAY,SAAS;IACnB,IAAI,IAAA;IACJ,QAAQ,IAAA;IACR,MAAM,IAAA;IACN,oBAAoB,IAAA;CACrB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@across-protocol/sdk",
3
3
  "author": "UMA Team",
4
- "version": "4.1.15",
4
+ "version": "4.1.17",
5
5
  "license": "AGPL-3.0",
6
6
  "homepage": "https://docs.across.to/reference/sdk",
7
7
  "files": [
@@ -48,6 +48,7 @@
48
48
  }
49
49
  ],
50
50
  "devDependencies": {
51
+ "@coral-xyz/borsh": "^0.30.1",
51
52
  "@defi-wonderland/smock": "^2.3.5",
52
53
  "@eth-optimism/contracts": "^0.5.37",
53
54
  "@nomiclabs/hardhat-ethers": "^2.2.1",
@@ -104,6 +105,7 @@
104
105
  "@eth-optimism/sdk": "^3.3.1",
105
106
  "@ethersproject/bignumber": "^5.7.0",
106
107
  "@pinata/sdk": "^2.1.0",
108
+ "@solana/web3.js": "^2.0.0",
107
109
  "@types/mocha": "^10.0.1",
108
110
  "@uma/sdk": "^0.34.10",
109
111
  "arweave": "^1.14.4",
@@ -8,6 +8,7 @@ export class CacheProvider extends RateLimitedProvider {
8
8
  public readonly getBlockByNumberPrefix: string;
9
9
  public readonly getLogsCachePrefix: string;
10
10
  public readonly callCachePrefix: string;
11
+ public readonly getTransactionReceiptPrefix: string;
11
12
  public readonly baseTTL: number;
12
13
 
13
14
  constructor(
@@ -29,6 +30,7 @@ export class CacheProvider extends RateLimitedProvider {
29
30
  this.getBlockByNumberPrefix = `${cachePrefix}:getBlockByNumber,`;
30
31
  this.getLogsCachePrefix = `${cachePrefix}:eth_getLogs,`;
31
32
  this.callCachePrefix = `${cachePrefix}:eth_call,`;
33
+ this.getTransactionReceiptPrefix = `${cachePrefix}:eth_getTransactionReceipt,`;
32
34
 
33
35
  const _ttlVar = providerCacheTtl;
34
36
  const _ttl = Number(_ttlVar);
@@ -38,7 +40,7 @@ export class CacheProvider extends RateLimitedProvider {
38
40
  this.baseTTL = _ttl;
39
41
  }
40
42
  override async send(method: string, params: Array<unknown>): Promise<unknown> {
41
- const cacheType = this.redisClient ? await this.cacheType(method, params) : CacheType.NONE;
43
+ let cacheType = this.redisClient ? await this.cacheType(method, params) : CacheType.NONE;
42
44
 
43
45
  if (cacheType !== CacheType.NONE) {
44
46
  const redisKey = this.buildRedisKey(method, params);
@@ -54,6 +56,11 @@ export class CacheProvider extends RateLimitedProvider {
54
56
  // Cache does not have the result. Query it directly and cache.
55
57
  const result = await super.send(method, params);
56
58
 
59
+ if (cacheType === CacheType.DECIDE_TTL_POST_SEND) {
60
+ const blockNumber = this.getBlockNumberFromRpcResponse(method, result);
61
+ cacheType = await this.cacheTypeForBlock(blockNumber);
62
+ }
63
+
57
64
  // Note: use swtich to ensure all enum cases are handled.
58
65
  switch (cacheType) {
59
66
  case CacheType.WITH_TTL:
@@ -78,7 +85,6 @@ export class CacheProvider extends RateLimitedProvider {
78
85
  }
79
86
 
80
87
  private buildRedisKey(method: string, params: Array<unknown>) {
81
- // Only handles eth_getLogs and eth_call right now.
82
88
  switch (method) {
83
89
  case "eth_getBlockByNumber":
84
90
  return this.getBlockByNumberPrefix + JSON.stringify(params);
@@ -86,6 +92,8 @@ export class CacheProvider extends RateLimitedProvider {
86
92
  return this.getLogsCachePrefix + JSON.stringify(params);
87
93
  case "eth_call":
88
94
  return this.callCachePrefix + JSON.stringify(params);
95
+ case "eth_getTransactionReceipt":
96
+ return this.getTransactionReceiptPrefix + JSON.stringify(params);
89
97
  default:
90
98
  throw new Error(`CacheProvider::buildRedisKey: invalid JSON-RPC method ${method}`);
91
99
  }
@@ -125,11 +133,24 @@ export class CacheProvider extends RateLimitedProvider {
125
133
 
126
134
  // If the block is old enough to cache, cache the call.
127
135
  return this.cacheTypeForBlock(blockNumber);
136
+ } else if ("eth_getTransactionReceipt" === method) {
137
+ // The only param to this request is "hash" so we can't determine how old the data we want is until after
138
+ // we receive the RPC result. Therefore we'll defer the TTL decision.
139
+ return Promise.resolve(CacheType.DECIDE_TTL_POST_SEND);
128
140
  } else {
129
141
  return Promise.resolve(CacheType.NONE);
130
142
  }
131
143
  }
132
144
 
145
+ private getBlockNumberFromRpcResponse(method: string, result: unknown): number {
146
+ if (method === "eth_getTransactionReceipt") {
147
+ const receipt = result as { blockNumber: number | string };
148
+ return Number(receipt.blockNumber);
149
+ } else {
150
+ throw new Error(`CacheProvider::getBlockNumberFromRpcResponse: unsupported JSON-RPC method ${method}`);
151
+ }
152
+ }
153
+
133
154
  private async cacheTypeForBlock(blockNumber: number): Promise<CacheType> {
134
155
  // Note: this method is an internal method provided by the BaseProvider. It allows the caller to specify a maxAge of
135
156
  // the block that is allowed. This means if a block has been retrieved within the last n seconds, no provider
@@ -6,3 +6,4 @@ export * from "./constants";
6
6
  export * from "./types";
7
7
  export * from "./utils";
8
8
  export * as mocks from "./mockProvider";
9
+ export * from "./solana";
@@ -302,13 +302,14 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
302
302
  }
303
303
 
304
304
  // The `data` member of err _may_ be populated but would need to be verified.
305
- const { message } = err;
305
+ const message = err.message.toLowerCase();
306
306
  switch (method) {
307
307
  case "eth_call":
308
308
  case "eth_estimateGas":
309
- return message.toLowerCase().includes("revert"); // Transaction will fail.
309
+ return message.includes("revert"); // Transaction will fail.
310
310
  case "eth_sendRawTransaction":
311
- return message.toLowerCase().includes("nonce"); // Nonce too low.
311
+ // Nonce too low or gas price is too low.
312
+ return message.includes("nonce") || message.includes("underpriced");
312
313
  default:
313
314
  break;
314
315
  }
@@ -0,0 +1,25 @@
1
+ import { ClusterUrl, createSolanaRpcFromTransport, RpcTransport } from "@solana/web3.js";
2
+
3
+ // This is abstract base class for creating Solana RPC clients and transports.
4
+ export abstract class SolanaBaseRpcFactory {
5
+ constructor(readonly chainId: number) {}
6
+
7
+ // This method must be implemented by the derived class to create a transport.
8
+ public abstract createTransport(): RpcTransport;
9
+
10
+ // This method creates a Solana RPC client from the implemented transport.
11
+ public createRpcClient() {
12
+ return createSolanaRpcFromTransport(this.createTransport());
13
+ }
14
+ }
15
+
16
+ // Enhanced class for creating Solana RPC clients and transports storing additional cluster info.
17
+ // This can be used by derived classes that are connected to a single base transport.
18
+ export abstract class SolanaClusterRpcFactory extends SolanaBaseRpcFactory {
19
+ constructor(
20
+ readonly clusterUrl: ClusterUrl,
21
+ ...baseConstructorParams: ConstructorParameters<typeof SolanaBaseRpcFactory>
22
+ ) {
23
+ super(...baseConstructorParams);
24
+ }
25
+ }
@@ -0,0 +1,13 @@
1
+ import { createDefaultRpcTransport, RpcTransport } from "@solana/web3.js";
2
+ import { SolanaClusterRpcFactory } from "./baseRpcFactories";
3
+
4
+ // Exposes default RPC transport for Solana in the SolanaClusterRpcFactory class.
5
+ export class SolanaDefaultRpcFactory extends SolanaClusterRpcFactory {
6
+ constructor(...clusterConstructorParams: ConstructorParameters<typeof SolanaClusterRpcFactory>) {
7
+ super(...clusterConstructorParams);
8
+ }
9
+
10
+ public createTransport(): RpcTransport {
11
+ return createDefaultRpcTransport({ url: this.clusterUrl });
12
+ }
13
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./baseRpcFactories";
2
+ export * from "./defaultRpcFactory";
3
+ export * from "./rateLimitedRpcFactory";
4
+ export * from "./utils";
@@ -0,0 +1,101 @@
1
+ import { RpcResponse, RpcTransport } from "@solana/web3.js";
2
+ import { QueueObject, queue } from "async";
3
+ import winston, { Logger } from "winston";
4
+ import { SolanaClusterRpcFactory } from "./baseRpcFactories";
5
+ import { SolanaDefaultRpcFactory } from "./defaultRpcFactory";
6
+ import { SolanaRateLimitTask } from "./utils";
7
+ import { getOriginFromURL } from "../../utils";
8
+
9
+ // This factory is a very small addition to the SolanaDefaultRpcFactory that ensures that no more than maxConcurrency
10
+ // requests are ever in flight. It uses the async/queue library to manage this.
11
+ export class RateLimitedSolanaRpcFactory extends SolanaClusterRpcFactory {
12
+ // The queue object that manages the tasks.
13
+ private queue: QueueObject<SolanaRateLimitTask>;
14
+
15
+ // Holds the underlying transport that the rate limiter wraps.
16
+ private readonly defaultTransport: RpcTransport;
17
+
18
+ // Takes the same arguments as the SolanaDefaultRpcFactory, but it has an additional parameters to control
19
+ // concurrency and logging at the beginning of the list.
20
+ constructor(
21
+ maxConcurrency: number,
22
+ readonly pctRpcCallsLogged: number,
23
+ readonly logger: Logger = winston.createLogger({
24
+ transports: [new winston.transports.Console()],
25
+ }),
26
+ ...defaultConstructorParams: ConstructorParameters<typeof SolanaDefaultRpcFactory>
27
+ ) {
28
+ super(...defaultConstructorParams);
29
+ this.defaultTransport = new SolanaDefaultRpcFactory(...defaultConstructorParams).createTransport();
30
+
31
+ // This sets up the queue. Each task is executed by forwarding the RPC request to the underlying base transport.
32
+ // This queue sends out requests concurrently, but stops once the concurrency limit is reached. The maxConcurrency
33
+ // is configured here.
34
+ this.queue = queue(async ({ rpcArgs, resolve, reject }: SolanaRateLimitTask, callback: () => void) => {
35
+ await this.wrapSendWithLog(...rpcArgs)
36
+ .then(resolve)
37
+ .catch(reject);
38
+ // we need this for the queue to know that the task is done
39
+ // @see: https://caolan.github.io/async/v3/global.html
40
+ callback();
41
+ }, maxConcurrency);
42
+ }
43
+
44
+ private async wrapSendWithLog(...rpcArgs: Parameters<RpcTransport>) {
45
+ if (this.pctRpcCallsLogged <= 0 || Math.random() > this.pctRpcCallsLogged / 100) {
46
+ // Non sample path: no logging or timing, just issue the request.
47
+ return await this.defaultTransport(...rpcArgs);
48
+ } else {
49
+ const payload = rpcArgs[0].payload as { method: string; params?: unknown[] };
50
+ const loggerArgs = {
51
+ at: "SolanaProviderUtils",
52
+ message: "Solana provider response sample",
53
+ provider: getOriginFromURL(this.clusterUrl),
54
+ method: payload.method,
55
+ params: payload.params,
56
+ chainId: this.chainId,
57
+ datadog: true,
58
+ };
59
+
60
+ // In this path we log an rpc response sample.
61
+ // Note: use performance.now() to ensure a purely monotonic clock.
62
+ const startTime = performance.now();
63
+ try {
64
+ const result = await this.defaultTransport(...rpcArgs);
65
+ const elapsedTimeS = (performance.now() - startTime) / 1000;
66
+ this.logger.debug({
67
+ ...loggerArgs,
68
+ success: true,
69
+ timeElapsed: elapsedTimeS,
70
+ });
71
+ return result;
72
+ } catch (error) {
73
+ // Log errors as well.
74
+ // For now, to keep logs light, don't log the error itself, just propagate and let it be handled higher up.
75
+ const elapsedTimeS = (performance.now() - startTime) / 1000;
76
+ this.logger.debug({
77
+ ...loggerArgs,
78
+ success: false,
79
+ timeElapsed: elapsedTimeS,
80
+ });
81
+ throw error;
82
+ }
83
+ }
84
+ }
85
+
86
+ public createTransport() {
87
+ return <TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> => {
88
+ // This simply creates a promise and adds the arguments and resolve and reject handlers to the task.
89
+ return new Promise<RpcResponse<TResponse>>((resolve, reject) => {
90
+ const task: SolanaRateLimitTask = {
91
+ rpcArgs: args,
92
+ resolve: resolve as (value: unknown) => void,
93
+ reject,
94
+ };
95
+ // We didn't previously wait for this push so we can emulate
96
+ // the same behavior with the `void` keyword.
97
+ void this.queue.push(task);
98
+ });
99
+ };
100
+ }
101
+ }
@@ -0,0 +1,14 @@
1
+ import { RpcTransport } from "@solana/rpc-spec";
2
+
3
+ /**
4
+ * This is the type we pass to define a Solana RPC request "task".
5
+ */
6
+ export interface SolanaRateLimitTask {
7
+ // These are the arguments to be passed to base transport when making the RPC request.
8
+ rpcArgs: Parameters<RpcTransport>;
9
+
10
+ // These are the promise callbacks that will cause the initial RPC call made by the user to either return a result
11
+ // or fail.
12
+ resolve: (result: unknown) => void;
13
+ reject: (err: unknown) => void;
14
+ }
@@ -169,4 +169,5 @@ export enum CacheType {
169
169
  NONE, // Do not cache
170
170
  WITH_TTL, // Cache with TTL
171
171
  NO_TTL, // Cache with infinite TTL
172
+ DECIDE_TTL_POST_SEND, // Decide which TTL to cache with after we receive the RPC response
172
173
  }