@talismn/chain-connectors 1.0.6 → 1.1.1

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.
package/README.md CHANGED
@@ -7,3 +7,13 @@ RPC connections to the networks that the Talisman wallet supports, one connector
7
7
  - `ChainConnectorSol`: Solana networks, `@solana/kit` RPC and transport per network (`getRpc`, `getTransport`, cached). The transport fails over through the network's RPCs in order and sends the next request to the RPC that answered last.
8
8
 
9
9
  The `*Stub` variants connect to a single network from its RPC list, without chaindata.
10
+
11
+ `ChainConnectorDotPapi` runs over a dapp's polkadot-api clients instead of opening sockets of its own. A dapp that uses `@talismn/balances` next to polkadot-api then has one socket per chain:
12
+
13
+ ```ts
14
+ const chainConnector = new ChainConnectorDotPapi((networkId) => clients[networkId])
15
+ ```
16
+
17
+ It supports the requests and the `state_subscribeStorage` subscription that balances uses. A storage subscription queries all its keys at each new best block and reports the values that changed, so each balances subscription costs one `state_queryStorageAt` per block.
18
+
19
+ Pass a client for every Polkadot SDK network that balances is enabled on, connected to full nodes over a websocket: balances uses the legacy JSON-RPC methods (`state_queryStorageAt`, `state_call`, `state_getKeysPaged`, …), which light clients do not serve. Keep each client alive as long as the connector, and change its RPCs with the provider's `switch()` rather than by recreating it: a subscription stays on the client it started with, and a destroyed client gives no signal, so its subscriptions stop updating.
package/dist/index.d.mts CHANGED
@@ -18,11 +18,24 @@ export declare class ChainConnectionError extends Error {
18
18
  chainId: string;
19
19
  constructor(chainId: string, options?: ErrorOptions);
20
20
  }
21
+ /**
22
+ * Passed to subscription callbacks when the chain has been unreachable for a while. Not
23
+ * terminal: the subscription stays registered and is re-issued when a socket connects.
24
+ */
21
25
  export declare class StaleRpcError extends Error {
22
26
  type: "STALE_RPC_ERROR";
23
27
  chainId: string;
24
28
  constructor(chainId: string, options?: ErrorOptions);
25
29
  }
30
+ export type ChainConnectorDotOptions = {
31
+ /**
32
+ * How long a new socket may take to open before the provider gives up on the endpoint
33
+ * and tries the next one. Defaults to polkadot-api's 5 seconds. A slow device or
34
+ * network can take longer than that to connect while it is busy, as a phone app is
35
+ * at startup, and every endpoint then times out in turn.
36
+ */
37
+ connectionTimeout?: number;
38
+ };
26
39
  /**
27
40
  * ChainConnector provides an interface similar to a websocket JSON-RPC provider, but with three points of difference:
28
41
  *
@@ -39,7 +52,7 @@ export declare class StaleRpcError extends Error {
39
52
  */
40
53
  export declare class ChainConnectorDot implements IChainConnectorDot {
41
54
  #private;
42
- constructor(chaindataChainProvider: IChaindataNetworkProvider);
55
+ constructor(chaindataChainProvider: IChaindataNetworkProvider, options?: ChainConnectorDotOptions);
43
56
  send<T = any>(chainId: DotNetworkId, method: string, params: unknown[], _isCacheable?: boolean | undefined, extraOptions?: {
44
57
  /**
45
58
  * Set to `true` if this query is speculative, i.e. if on some chains it's expected that it will raise a wasm unreachable error of the form:
@@ -81,6 +94,40 @@ export declare class ChainConnectorDot implements IChainConnectorDot {
81
94
  private getTalismanSub;
82
95
  }
83
96
  //#endregion
97
+ //#region src/dot/ChainConnectorDotPapi.d.ts
98
+ /** The parts of a polkadot-api `PolkadotClient` that the connector uses */
99
+ export type PapiClient = {
100
+ /** `PolkadotClient` types it without `abortSignal`, but it is substrate-client's `request`, which honours it */
101
+ _request: <Reply>(method: string, params: unknown[], abortSignal?: AbortSignal) => Promise<Reply>;
102
+ bestBlocks$: {
103
+ subscribe: (observer: {
104
+ next: (blocks: {
105
+ hash: string;
106
+ }[]) => void;
107
+ error: (error: unknown) => void;
108
+ complete: () => void;
109
+ }) => {
110
+ unsubscribe: () => void;
111
+ };
112
+ };
113
+ };
114
+ /**
115
+ * Runs `@talismn/balances` over a dapp's polkadot-api clients, so the dapp and its balances share one socket per chain.
116
+ *
117
+ * `state_subscribeStorage` is the only subscription balances uses. It is served by querying the keys at each new
118
+ * best block and reporting the values that changed, like the node does. One query runs at a time, for the latest
119
+ * best block. A failed query is retried at the next block, because balances treats a subscription error as final
120
+ * and would stop updating that network. Only when the first three queries fail does the subscription report the error.
121
+ */
122
+ export declare class ChainConnectorDotPapi implements IChainConnectorDot {
123
+ #private;
124
+ constructor(clientFor: (networkId: DotNetworkId) => PapiClient | undefined);
125
+ send<T = unknown>(networkId: DotNetworkId, method: string, params: unknown[]): Promise<T>;
126
+ subscribe(networkId: DotNetworkId, subscribeMethod: string, _responseMethod: string, params: unknown[], callback: SubscriptionCallback, timeout?: number | false): Promise<(unsubscribeMethod: string) => void>;
127
+ reset(): Promise<void>;
128
+ private getClient;
129
+ }
130
+ //#endregion
84
131
  //#region src/dot/ChainConnectorDotStub.d.ts
85
132
  export declare class ChainConnectorDotStub implements IChainConnectorDot {
86
133
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/dot/IChainConnectorDot.ts","../src/dot/ChainConnectorDot.ts","../src/dot/ChainConnectorDotStub.ts","../src/eth/IChainConnectorEth.ts","../src/eth/ChainConnectorEth.ts","../src/eth/ChainConnectorEthStub.ts","../src/sol/getSolRpc.ts","../src/sol/IChainConnectorSol.ts","../src/sol/ChainConnectorSol.ts","../src/sol/ChainConnectorSolStub.ts"],"mappings":";;;;;YAIY,wBAAwB,OAAO,cAAc;iBAExC;EACf,KAAK,aACH,SAAS,cACT,gBACA,mBACA,uBACA;IACE;MAED,QAAQ;EAEX,UACE,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EAEZ,MAAM,SAAS,eAAe;;;;qBCCnB,6BAA6B;EACxC;EACA;EAEA,YAAY,iBAAiB,UAAU;;qBAQ5B,sBAAsB;EACjC;EACA;EAEA,YAAY,iBAAiB,UAAU;;;;;;;;;;;;;;;;qBA+C5B,6BAA6B;;EAMxC,YAAY,wBAAwB;EAK9B,KAAK,SACT,SAAS,cACT,gBACA,mBACA,oCACA;;;;;;;;;;IAUE;MAED,QAAQ;EAsDL,UACJ,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;;;;;;EA6FN,MAAM,SAAS,eAAY;;UAyBzB;;UA0BA;;;;;;UAgCM;UAuBA;UA8BN;UAoDA;UAWA;UAsBM;;UAYN;;UAQA;UAIA;;;;qBC5fG,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,KAAK,aACT,UAAU,cACV,gBACA,mBACA,yBACC,QAAQ;EAOL,UACJ,UAAU,cACV,yBACA,yBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EA4BZ,SAAS;EAIT;;;;iBChEe;EACf,+BAA+B,cAAc,iBAAiB,QAAQ;EACtE,+BACE,cAAc,cACd,0BAA0B,YACvB,QAAQ;EACb,yBAAyB,eAAe;;;;qBCE7B,6BAA6B;;EAGxC,YAAY,mBAAmB,4BAA4B;EAIrD,6BAA6B,cAAc,eAAe,QAAQ;EAOlE,6BACJ,cAAc,cACd,0BAA0B,UACzB,QAAQ;EAOJ,uBAAuB,eAAe;;;;qBC5BlC,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,gCAAgC,QAAQ;EAIxC,6BACJ,YAAY,cACZ,0BAA0B,UACzB,QAAQ;EAIX;;;;YCrBU,SAAS,IAAI;YAqHb;;EAEV,gBAAgB;;;;;;;qBAuDL,kBAAe,YACd,cAAY,gBACV,UACL,wBACR;qBAIU,YAAS,WACT,cAAY,gBACT,UACJ,wBACT;;;iBCzLc;EACf,SAAS,WAAW,iBAAiB,QAAQ;;EAE7C,eAAe,WAAW,iBAAiB,QAAQ;;;;qBCIxC,6BAA6B;;EAKxC,YAAY,mBAAmB,4BAA4B;EAWrD,aAAa,WAAW,eAAe,QAAQ;EAU/C,OAAO,WAAW,eAAe,QAAQ;;EAQ/C,uBAAuB,YAAY;;;;qBCtCxB,iCAAiC;;EAI5C,YAAY,SAAS,KAAK;EAKpB,UAAU,QAAQ;EAIlB,gBAAgB,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/dot/IChainConnectorDot.ts","../src/dot/ChainConnectorDot.ts","../src/dot/ChainConnectorDotPapi.ts","../src/dot/ChainConnectorDotStub.ts","../src/eth/IChainConnectorEth.ts","../src/eth/ChainConnectorEth.ts","../src/eth/ChainConnectorEthStub.ts","../src/sol/getSolRpc.ts","../src/sol/IChainConnectorSol.ts","../src/sol/ChainConnectorSol.ts","../src/sol/ChainConnectorSolStub.ts"],"mappings":";;;;;YAIY,wBAAwB,OAAO,cAAc;iBAExC;EACf,KAAK,aACH,SAAS,cACT,gBACA,mBACA,uBACA;IACE;MAED,QAAQ;EAEX,UACE,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EAEZ,MAAM,SAAS,eAAe;;;;qBCOnB,6BAA6B;EACxC;EACA;EAEA,YAAY,iBAAiB,UAAU;;;;;;qBAY5B,sBAAsB;EACjC;EACA;EAEA,YAAY,iBAAiB,UAAU;;YAmC7B;;;;;;;EAOV;;;;;;;;;;;;;;;;qBAiBW,6BAA6B;;EAOxC,YACE,wBAAwB,2BACxB,UAAU;EAON,KAAK,SACT,SAAS,cACT,gBACA,mBACA,oCACA;;;;;;;;;;IAUE;MAED,QAAQ;EAsDL,UACJ,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;;;;;;EA2FN,MAAM,SAAS,eAAY;;UA2BzB;;UA0BA;;;;;;UAgCM;UA2BA;UAgCN;UAoDA;UAeA;UAwBM;;UAYN;;UAQA;UAIA;;;;;YC/hBE;;EAEV,WAAW,OAAO,gBAAgB,mBAAmB,cAAc,gBAAgB,QAAQ;EAC3F;IACE,YAAY;MACV,OAAO;QAAU;;MACjB,QAAQ;MACR;;MACM;;;;;;;;;;;;qBAYC,iCAAiC;;EAG5C,YAAY,YAAY,WAAW,iBAAiB;EAI9C,KAAK,aAAa,WAAW,cAAc,gBAAgB,oBAAoB,QAAQ;EAIvF,UACJ,WAAW,cACX,yBACA,yBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EA8FN,SAAK;UAEH;;;;qBC1IG,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,KAAK,aACT,UAAU,cACV,gBACA,mBACA,yBACC,QAAQ;EAOL,UACJ,UAAU,cACV,yBACA,yBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EA4BZ,SAAS;EAIT;;;;iBChEe;EACf,+BAA+B,cAAc,iBAAiB,QAAQ;EACtE,+BACE,cAAc,cACd,0BAA0B,YACvB,QAAQ;EACb,yBAAyB,eAAe;;;;qBCE7B,6BAA6B;;EAGxC,YAAY,mBAAmB,4BAA4B;EAIrD,6BAA6B,cAAc,eAAe,QAAQ;EAOlE,6BACJ,cAAc,cACd,0BAA0B,UACzB,QAAQ;EAOJ,uBAAuB,eAAe;;;;qBC5BlC,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,gCAAgC,QAAQ;EAIxC,6BACJ,YAAY,cACZ,0BAA0B,UACzB,QAAQ;EAIX;;;;YCrBU,SAAS,IAAI;YAqHb;;EAEV,gBAAgB;;;;;;;qBAuDL,kBAAe,YACd,cAAY,gBACV,UACL,wBACR;qBAIU,YAAS,WACT,cAAY,gBACT,UACJ,wBACT;;;iBCzLc;EACf,SAAS,WAAW,iBAAiB,QAAQ;;EAE7C,eAAe,WAAW,iBAAiB,QAAQ;;;;qBCIxC,6BAA6B;;EAKxC,YAAY,mBAAmB,4BAA4B;EAWrD,aAAa,WAAW,eAAe,QAAQ;EAU/C,OAAO,WAAW,eAAe,QAAQ;;EAQ/C,uBAAuB,YAAY;;;;qBCtCxB,iCAAiC;;EAI5C,YAAY,SAAS,KAAK;EAKpB,UAAU,QAAQ;EAIlB,gBAAgB,QAAQ"}
package/dist/index.d.ts CHANGED
@@ -18,11 +18,24 @@ export declare class ChainConnectionError extends Error {
18
18
  chainId: string;
19
19
  constructor(chainId: string, options?: ErrorOptions);
20
20
  }
21
+ /**
22
+ * Passed to subscription callbacks when the chain has been unreachable for a while. Not
23
+ * terminal: the subscription stays registered and is re-issued when a socket connects.
24
+ */
21
25
  export declare class StaleRpcError extends Error {
22
26
  type: "STALE_RPC_ERROR";
23
27
  chainId: string;
24
28
  constructor(chainId: string, options?: ErrorOptions);
25
29
  }
30
+ export type ChainConnectorDotOptions = {
31
+ /**
32
+ * How long a new socket may take to open before the provider gives up on the endpoint
33
+ * and tries the next one. Defaults to polkadot-api's 5 seconds. A slow device or
34
+ * network can take longer than that to connect while it is busy, as a phone app is
35
+ * at startup, and every endpoint then times out in turn.
36
+ */
37
+ connectionTimeout?: number;
38
+ };
26
39
  /**
27
40
  * ChainConnector provides an interface similar to a websocket JSON-RPC provider, but with three points of difference:
28
41
  *
@@ -39,7 +52,7 @@ export declare class StaleRpcError extends Error {
39
52
  */
40
53
  export declare class ChainConnectorDot implements IChainConnectorDot {
41
54
  #private;
42
- constructor(chaindataChainProvider: IChaindataNetworkProvider);
55
+ constructor(chaindataChainProvider: IChaindataNetworkProvider, options?: ChainConnectorDotOptions);
43
56
  send<T = any>(chainId: DotNetworkId, method: string, params: unknown[], _isCacheable?: boolean | undefined, extraOptions?: {
44
57
  /**
45
58
  * Set to `true` if this query is speculative, i.e. if on some chains it's expected that it will raise a wasm unreachable error of the form:
@@ -81,6 +94,40 @@ export declare class ChainConnectorDot implements IChainConnectorDot {
81
94
  private getTalismanSub;
82
95
  }
83
96
  //#endregion
97
+ //#region src/dot/ChainConnectorDotPapi.d.ts
98
+ /** The parts of a polkadot-api `PolkadotClient` that the connector uses */
99
+ export type PapiClient = {
100
+ /** `PolkadotClient` types it without `abortSignal`, but it is substrate-client's `request`, which honours it */
101
+ _request: <Reply>(method: string, params: unknown[], abortSignal?: AbortSignal) => Promise<Reply>;
102
+ bestBlocks$: {
103
+ subscribe: (observer: {
104
+ next: (blocks: {
105
+ hash: string;
106
+ }[]) => void;
107
+ error: (error: unknown) => void;
108
+ complete: () => void;
109
+ }) => {
110
+ unsubscribe: () => void;
111
+ };
112
+ };
113
+ };
114
+ /**
115
+ * Runs `@talismn/balances` over a dapp's polkadot-api clients, so the dapp and its balances share one socket per chain.
116
+ *
117
+ * `state_subscribeStorage` is the only subscription balances uses. It is served by querying the keys at each new
118
+ * best block and reporting the values that changed, like the node does. One query runs at a time, for the latest
119
+ * best block. A failed query is retried at the next block, because balances treats a subscription error as final
120
+ * and would stop updating that network. Only when the first three queries fail does the subscription report the error.
121
+ */
122
+ export declare class ChainConnectorDotPapi implements IChainConnectorDot {
123
+ #private;
124
+ constructor(clientFor: (networkId: DotNetworkId) => PapiClient | undefined);
125
+ send<T = unknown>(networkId: DotNetworkId, method: string, params: unknown[]): Promise<T>;
126
+ subscribe(networkId: DotNetworkId, subscribeMethod: string, _responseMethod: string, params: unknown[], callback: SubscriptionCallback, timeout?: number | false): Promise<(unsubscribeMethod: string) => void>;
127
+ reset(): Promise<void>;
128
+ private getClient;
129
+ }
130
+ //#endregion
84
131
  //#region src/dot/ChainConnectorDotStub.d.ts
85
132
  export declare class ChainConnectorDotStub implements IChainConnectorDot {
86
133
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/dot/IChainConnectorDot.ts","../src/dot/ChainConnectorDot.ts","../src/dot/ChainConnectorDotStub.ts","../src/eth/IChainConnectorEth.ts","../src/eth/ChainConnectorEth.ts","../src/eth/ChainConnectorEthStub.ts","../src/sol/getSolRpc.ts","../src/sol/IChainConnectorSol.ts","../src/sol/ChainConnectorSol.ts","../src/sol/ChainConnectorSolStub.ts"],"mappings":";;;;;YAIY,wBAAwB,OAAO,cAAc;iBAExC;EACf,KAAK,aACH,SAAS,cACT,gBACA,mBACA,uBACA;IACE;MAED,QAAQ;EAEX,UACE,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EAEZ,MAAM,SAAS,eAAe;;;;qBCCnB,6BAA6B;EACxC;EACA;EAEA,YAAY,iBAAiB,UAAU;;qBAQ5B,sBAAsB;EACjC;EACA;EAEA,YAAY,iBAAiB,UAAU;;;;;;;;;;;;;;;;qBA+C5B,6BAA6B;;EAMxC,YAAY,wBAAwB;EAK9B,KAAK,SACT,SAAS,cACT,gBACA,mBACA,oCACA;;;;;;;;;;IAUE;MAED,QAAQ;EAsDL,UACJ,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;;;;;;EA6FN,MAAM,SAAS,eAAY;;UAyBzB;;UA0BA;;;;;;UAgCM;UAuBA;UA8BN;UAoDA;UAWA;UAsBM;;UAYN;;UAQA;UAIA;;;;qBC5fG,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,KAAK,aACT,UAAU,cACV,gBACA,mBACA,yBACC,QAAQ;EAOL,UACJ,UAAU,cACV,yBACA,yBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EA4BZ,SAAS;EAIT;;;;iBChEe;EACf,+BAA+B,cAAc,iBAAiB,QAAQ;EACtE,+BACE,cAAc,cACd,0BAA0B,YACvB,QAAQ;EACb,yBAAyB,eAAe;;;;qBCE7B,6BAA6B;;EAGxC,YAAY,mBAAmB,4BAA4B;EAIrD,6BAA6B,cAAc,eAAe,QAAQ;EAOlE,6BACJ,cAAc,cACd,0BAA0B,UACzB,QAAQ;EAOJ,uBAAuB,eAAe;;;;qBC5BlC,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,gCAAgC,QAAQ;EAIxC,6BACJ,YAAY,cACZ,0BAA0B,UACzB,QAAQ;EAIX;;;;YCrBU,SAAS,IAAI;YAqHb;;EAEV,gBAAgB;;;;;;;qBAuDL,kBAAe,YACd,cAAY,gBACV,UACL,wBACR;qBAIU,YAAS,WACT,cAAY,gBACT,UACJ,wBACT;;;iBCzLc;EACf,SAAS,WAAW,iBAAiB,QAAQ;;EAE7C,eAAe,WAAW,iBAAiB,QAAQ;;;;qBCIxC,6BAA6B;;EAKxC,YAAY,mBAAmB,4BAA4B;EAWrD,aAAa,WAAW,eAAe,QAAQ;EAU/C,OAAO,WAAW,eAAe,QAAQ;;EAQ/C,uBAAuB,YAAY;;;;qBCtCxB,iCAAiC;;EAI5C,YAAY,SAAS,KAAK;EAKpB,UAAU,QAAQ;EAIlB,gBAAgB,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/dot/IChainConnectorDot.ts","../src/dot/ChainConnectorDot.ts","../src/dot/ChainConnectorDotPapi.ts","../src/dot/ChainConnectorDotStub.ts","../src/eth/IChainConnectorEth.ts","../src/eth/ChainConnectorEth.ts","../src/eth/ChainConnectorEthStub.ts","../src/sol/getSolRpc.ts","../src/sol/IChainConnectorSol.ts","../src/sol/ChainConnectorSol.ts","../src/sol/ChainConnectorSolStub.ts"],"mappings":";;;;;YAIY,wBAAwB,OAAO,cAAc;iBAExC;EACf,KAAK,aACH,SAAS,cACT,gBACA,mBACA,uBACA;IACE;MAED,QAAQ;EAEX,UACE,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EAEZ,MAAM,SAAS,eAAe;;;;qBCOnB,6BAA6B;EACxC;EACA;EAEA,YAAY,iBAAiB,UAAU;;;;;;qBAY5B,sBAAsB;EACjC;EACA;EAEA,YAAY,iBAAiB,UAAU;;YAmC7B;;;;;;;EAOV;;;;;;;;;;;;;;;;qBAiBW,6BAA6B;;EAOxC,YACE,wBAAwB,2BACxB,UAAU;EAON,KAAK,SACT,SAAS,cACT,gBACA,mBACA,oCACA;;;;;;;;;;IAUE;MAED,QAAQ;EAsDL,UACJ,SAAS,cACT,yBACA,wBACA,mBACA,UAAU,sBACV,2BACC,SAAS;;;;;;EA2FN,MAAM,SAAS,eAAY;;UA2BzB;;UA0BA;;;;;;UAgCM;UA2BA;UAgCN;UAoDA;UAeA;UAwBM;;UAYN;;UAQA;UAIA;;;;;YC/hBE;;EAEV,WAAW,OAAO,gBAAgB,mBAAmB,cAAc,gBAAgB,QAAQ;EAC3F;IACE,YAAY;MACV,OAAO;QAAU;;MACjB,QAAQ;MACR;;MACM;;;;;;;;;;;;qBAYC,iCAAiC;;EAG5C,YAAY,YAAY,WAAW,iBAAiB;EAI9C,KAAK,aAAa,WAAW,cAAc,gBAAgB,oBAAoB,QAAQ;EAIvF,UACJ,WAAW,cACX,yBACA,yBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EA8FN,SAAK;UAEH;;;;qBC1IG,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,KAAK,aACT,UAAU,cACV,gBACA,mBACA,yBACC,QAAQ;EAOL,UACJ,UAAU,cACV,yBACA,yBACA,mBACA,UAAU,sBACV,2BACC,SAAS;EA4BZ,SAAS;EAIT;;;;iBChEe;EACf,+BAA+B,cAAc,iBAAiB,QAAQ;EACtE,+BACE,cAAc,cACd,0BAA0B,YACvB,QAAQ;EACb,yBAAyB,eAAe;;;;qBCE7B,6BAA6B;;EAGxC,YAAY,mBAAmB,4BAA4B;EAIrD,6BAA6B,cAAc,eAAe,QAAQ;EAOlE,6BACJ,cAAc,cACd,0BAA0B,UACzB,QAAQ;EAOJ,uBAAuB,eAAe;;;;qBC5BlC,iCAAiC;;EAG5C,YAAY,SAAS;EAIf,gCAAgC,QAAQ;EAIxC,6BACJ,YAAY,cACZ,0BAA0B,UACzB,QAAQ;EAIX;;;;YCrBU,SAAS,IAAI;YAqHb;;EAEV,gBAAgB;;;;;;;qBAuDL,kBAAe,YACd,cAAY,gBACV,UACL,wBACR;qBAIU,YAAS,WACT,cAAY,gBACT,UACJ,wBACT;;;iBCzLc;EACf,SAAS,WAAW,iBAAiB,QAAQ;;EAE7C,eAAe,WAAW,iBAAiB,QAAQ;;;;qBCIxC,6BAA6B;;EAKxC,YAAY,mBAAmB,4BAA4B;EAWrD,aAAa,WAAW,eAAe,QAAQ;EAU/C,OAAO,WAAW,eAAe,QAAQ;;EAQ/C,uBAAuB,YAAY;;;;qBCtCxB,iCAAiC;;EAI5C,YAAY,SAAS,KAAK;EAKpB,UAAU,QAAQ;EAIlB,gBAAgB,QAAQ"}
package/dist/index.js CHANGED
@@ -40,8 +40,14 @@ const BAD_RPC_ERRORS = {
40
40
  "-32097": "Rate limit exceeded",
41
41
  "-32098": "Capacity exceeded"
42
42
  };
43
- const RESPONSE_TIMEOUT = 3e4;
43
+ const RESPONSE_TIMEOUT$1 = 3e4;
44
44
  const KEEP_ALIVE_INTERVAL = 2e4;
45
+ /**
46
+ * How long a connection stays open once its last user releases it. A one-off request is
47
+ * often followed by more traffic to the same chain (balances read the runtime version,
48
+ * then subscribe to storage), which then reuses the socket instead of opening a new one.
49
+ */
50
+ const IDLE_CLOSE_DELAY = 3e4;
45
51
  /** in-flight requests reject with DestroyedError when a connection is torn down - expected, not worth logging */
46
52
  const isDestroyedError = (error) => error instanceof Error && error.name === "DestroyedError";
47
53
  const STALE_NOTIFY_TIMEOUT = 3e4;
@@ -54,6 +60,10 @@ var ChainConnectionError = class extends Error {
54
60
  this.chainId = chainId;
55
61
  }
56
62
  };
63
+ /**
64
+ * Passed to subscription callbacks when the chain has been unreachable for a while. Not
65
+ * terminal: the subscription stays registered and is re-issued when a socket connects.
66
+ */
57
67
  var StaleRpcError = class extends Error {
58
68
  type;
59
69
  chainId;
@@ -79,10 +89,12 @@ var StaleRpcError = class extends Error {
79
89
  */
80
90
  var ChainConnectorDot = class {
81
91
  #chaindataChainProvider;
92
+ #connectionTimeout;
82
93
  #connections = {};
83
94
  #pendingConnections = {};
84
- constructor(chaindataChainProvider) {
95
+ constructor(chaindataChainProvider, options) {
85
96
  this.#chaindataChainProvider = chaindataChainProvider;
97
+ this.#connectionTimeout = options?.connectionTimeout;
86
98
  }
87
99
  async send(chainId, method, params, _isCacheable, extraOptions) {
88
100
  const talismanSub = this.getTalismanSub();
@@ -171,8 +183,7 @@ var ChainConnectorDot = class {
171
183
  subscription.cancelRequest?.();
172
184
  subscription.stopFollow?.();
173
185
  const current = this.#connections[chainId];
174
- const isLastUser = !!current && current.users.size === 1 && current.users.has(socketUserId);
175
- if (subscription.serverSubId !== null && current && !isLastUser) try {
186
+ if (subscription.serverSubId !== null && current) try {
176
187
  current.client.request(unsubscribeMethod, [subscription.serverSubId]).catch((error) => {
177
188
  if (!isDestroyedError(error)) log_default.warn(`Failed to unsubscribe from ${chainId}`, error);
178
189
  });
@@ -202,11 +213,11 @@ var ChainConnectorDot = class {
202
213
  for (const subscription of fresh.subscriptions) this.startSubscription(fresh, subscription);
203
214
  } catch (error) {
204
215
  log_default.warn(`Failed to recreate connection for ${chainId} after reset`, error);
205
- for (const subscription of connection.subscriptions) subscription.callback(new StaleRpcError(chainId, { cause: error }), null);
216
+ for (const subscription of connection.subscriptions) subscription.callback(new ChainConnectionError(chainId, { cause: error }), null);
206
217
  }
207
218
  }
208
219
  /** Sends a request over a connection, throwing `Error("TIMEOUT")` if no response arrives in time */
209
- request(connection, method, params, timeoutMs = RESPONSE_TIMEOUT) {
220
+ request(connection, method, params, timeoutMs = RESPONSE_TIMEOUT$1) {
210
221
  return new Promise((resolve, reject) => {
211
222
  const timer = setTimeout(() => {
212
223
  cancel();
@@ -260,6 +271,10 @@ var ChainConnectorDot = class {
260
271
  });
261
272
  connection = await this.#pendingConnections[chainId];
262
273
  }
274
+ if (connection.idleTimeout) {
275
+ clearTimeout(connection.idleTimeout);
276
+ connection.idleTimeout = null;
277
+ }
263
278
  const socketUserId = this.getExclusiveRandomId([...connection.users]);
264
279
  connection.users.add(socketUserId);
265
280
  return [socketUserId, connection];
@@ -270,7 +285,10 @@ var ChainConnectorDot = class {
270
285
  const rpcs = chain.rpcs.concat();
271
286
  if (!rpcs.length) throw new Error(`No healthy RPCs available for chain ${chainId}`);
272
287
  let connection = null;
273
- const provider = (0, _polkadot_api_ws_provider.getWsProvider)(rpcs, { onStatusChanged: (status) => this.handleStatusChange(connection, status) });
288
+ const provider = (0, _polkadot_api_ws_provider.getWsProvider)(rpcs, {
289
+ onStatusChanged: (status) => this.handleStatusChange(connection, status),
290
+ ...this.#connectionTimeout !== void 0 && { timeout: this.#connectionTimeout }
291
+ });
274
292
  connection = {
275
293
  chainId,
276
294
  provider,
@@ -279,7 +297,8 @@ var ChainConnectorDot = class {
279
297
  subscriptions: /* @__PURE__ */ new Set(),
280
298
  wasConnected: false,
281
299
  keepAliveInterval: null,
282
- staleTimeout: null
300
+ staleTimeout: null,
301
+ idleTimeout: null
283
302
  };
284
303
  return connection;
285
304
  }
@@ -321,15 +340,21 @@ var ChainConnectorDot = class {
321
340
  const connection = this.#connections[chainId];
322
341
  if (!connection) return;
323
342
  connection.users.delete(socketUserId);
324
- if (connection.users.size > 0) return;
325
- this.destroyConnection(connection);
326
- delete this.#connections[chainId];
343
+ if (connection.users.size > 0 || connection.idleTimeout) return;
344
+ connection.idleTimeout = setTimeout(() => {
345
+ connection.idleTimeout = null;
346
+ if (connection.users.size > 0 || this.#connections[chainId] !== connection) return;
347
+ this.destroyConnection(connection);
348
+ delete this.#connections[chainId];
349
+ }, IDLE_CLOSE_DELAY);
327
350
  }
328
351
  destroyConnection(connection) {
329
352
  if (connection.keepAliveInterval) clearInterval(connection.keepAliveInterval);
330
353
  if (connection.staleTimeout) clearTimeout(connection.staleTimeout);
354
+ if (connection.idleTimeout) clearTimeout(connection.idleTimeout);
331
355
  connection.keepAliveInterval = null;
332
356
  connection.staleTimeout = null;
357
+ connection.idleTimeout = null;
333
358
  for (const subscription of connection.subscriptions) {
334
359
  subscription.stopFollow?.();
335
360
  subscription.cancelRequest?.();
@@ -376,6 +401,119 @@ var ChainConnectorDot = class {
376
401
  }
377
402
  };
378
403
  //#endregion
404
+ //#region src/dot/ChainConnectorDotPapi.ts
405
+ const RESPONSE_TIMEOUT = 3e4;
406
+ const FAILURES_BEFORE_FIRST_RESULT = 3;
407
+ /**
408
+ * Runs `@talismn/balances` over a dapp's polkadot-api clients, so the dapp and its balances share one socket per chain.
409
+ *
410
+ * `state_subscribeStorage` is the only subscription balances uses. It is served by querying the keys at each new
411
+ * best block and reporting the values that changed, like the node does. One query runs at a time, for the latest
412
+ * best block. A failed query is retried at the next block, because balances treats a subscription error as final
413
+ * and would stop updating that network. Only when the first three queries fail does the subscription report the error.
414
+ */
415
+ var ChainConnectorDotPapi = class {
416
+ #clientFor;
417
+ constructor(clientFor) {
418
+ this.#clientFor = clientFor;
419
+ }
420
+ async send(networkId, method, params) {
421
+ return requestWithTimeout(this.getClient(networkId), method, params, new AbortController());
422
+ }
423
+ async subscribe(networkId, subscribeMethod, _responseMethod, params, callback, timeout = 3e4) {
424
+ const client = this.getClient(networkId);
425
+ if (subscribeMethod !== "state_subscribeStorage") throw new Error(`ChainConnectorDotPapi does not support ${subscribeMethod}`);
426
+ const [keys] = params;
427
+ const values = /* @__PURE__ */ new Map();
428
+ let bestBlockHash;
429
+ let queriedBlockHash;
430
+ let inFlight;
431
+ let delivered = false;
432
+ let failures = 0;
433
+ let active = true;
434
+ let subscription;
435
+ const staleTimer = timeout ? setTimeout(() => callback(new StaleRpcError(networkId), null), timeout) : void 0;
436
+ const stop = () => {
437
+ active = false;
438
+ clearTimeout(staleTimer);
439
+ inFlight?.abort();
440
+ subscription?.unsubscribe();
441
+ };
442
+ const finish = (error) => {
443
+ if (!active) return;
444
+ stop();
445
+ callback(error, null);
446
+ };
447
+ const reportChanges = (result) => {
448
+ const changes = result.changes.filter(([key, value]) => values.get(key) !== value);
449
+ for (const [key, value] of changes) values.set(key, value);
450
+ if (!changes.length) return;
451
+ delivered = true;
452
+ clearTimeout(staleTimer);
453
+ callback(null, {
454
+ block: result.block,
455
+ changes
456
+ });
457
+ };
458
+ const reportFailure = (error) => {
459
+ if (delivered || ++failures < FAILURES_BEFORE_FIRST_RESULT) return log_default.warn(`state_queryStorageAt failed on ${networkId}, retrying at the next block`, error);
460
+ finish(error);
461
+ };
462
+ const queryBestBlock = async () => {
463
+ if (!active || inFlight || !bestBlockHash || bestBlockHash === queriedBlockHash) return;
464
+ const blockHash = bestBlockHash;
465
+ queriedBlockHash = blockHash;
466
+ const controller = new AbortController();
467
+ inFlight = controller;
468
+ try {
469
+ const [result] = await requestWithTimeout(client, "state_queryStorageAt", [keys, blockHash], controller);
470
+ if (!active) return;
471
+ if (!result) throw new Error(`Empty state_queryStorageAt response on ${networkId}`);
472
+ reportChanges(result);
473
+ } catch (error) {
474
+ if (active) reportFailure(error);
475
+ } finally {
476
+ if (inFlight === controller) inFlight = void 0;
477
+ queryBestBlock();
478
+ }
479
+ };
480
+ try {
481
+ subscription = client.bestBlocks$.subscribe({
482
+ next: ([best]) => {
483
+ bestBlockHash = best?.hash;
484
+ queryBestBlock();
485
+ },
486
+ error: (error) => finish(error),
487
+ complete: () => finish(/* @__PURE__ */ new Error(`polkadot-api client for ${networkId} stopped following blocks`))
488
+ });
489
+ } catch (error) {
490
+ stop();
491
+ throw error;
492
+ }
493
+ if (!active) subscription.unsubscribe();
494
+ return stop;
495
+ }
496
+ async reset() {}
497
+ getClient(networkId) {
498
+ const client = this.#clientFor(networkId);
499
+ if (!client) throw new Error(`No polkadot-api client for network ${networkId}`);
500
+ return client;
501
+ }
502
+ };
503
+ /** rejects on its own timer too, so a client that ignores the abort signal cannot hang the caller */
504
+ const requestWithTimeout = (client, method, params, controller) => new Promise((resolve, reject) => {
505
+ const timer = setTimeout(() => {
506
+ controller.abort();
507
+ reject(/* @__PURE__ */ new Error("Timeout"));
508
+ }, RESPONSE_TIMEOUT);
509
+ try {
510
+ client._request(method, params, controller.signal).then(resolve, reject).finally(() => clearTimeout(timer));
511
+ } catch (error) {
512
+ clearTimeout(timer);
513
+ reject(error);
514
+ }
515
+ });
516
+ //#endregion
379
517
  //#region src/dot/ChainConnectorDotStub.ts
380
518
  const TIMEOUT = 1e4;
381
519
  var ChainConnectorDotStub = class {
@@ -733,6 +871,7 @@ var ChainConnectorSolStub = class {
733
871
  //#endregion
734
872
  exports.ChainConnectionError = ChainConnectionError;
735
873
  exports.ChainConnectorDot = ChainConnectorDot;
874
+ exports.ChainConnectorDotPapi = ChainConnectorDotPapi;
736
875
  exports.ChainConnectorDotStub = ChainConnectorDotStub;
737
876
  exports.ChainConnectorEth = ChainConnectorEth;
738
877
  exports.ChainConnectorEthStub = ChainConnectorEthStub;