@openzeppelin/relayer-plugin-channels 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -229,6 +229,10 @@ export API_KEY_HEADER="x-api-key" # Header name to extract API key (defa
229
229
  # Contract capacity limits (optional)
230
230
  export LIMITED_CONTRACTS="CDL74RF5BLYR2YBLCCI7F5FB6TPSCLKEJUBSD2RSVWZ4YHF3VMFAIGWA" # Comma-separated contract addresses
231
231
  export CONTRACT_CAPACITY_RATIO=0.8 # Max ratio of pool for limited contracts (default: 0.8 = 80%)
232
+
233
+ # Inclusion fee overrides (optional)
234
+ export INCLUSION_FEE_DEFAULT=203 # Inclusion fee in stroops for regular contracts (default: BASE_FEE * 2 + 3 = 203)
235
+ export INCLUSION_FEE_LIMITED=201 # Inclusion fee in stroops for limited contracts (default: BASE_FEE * 2 + 1 = 201)
232
236
  ```
233
237
 
234
238
  Your Relayer should now contain:
@@ -510,6 +514,49 @@ curl -X POST http://localhost:8080/api/v1/plugins/channels/call \
510
514
  }
511
515
  ```
512
516
 
517
+ ### Get Pool Stats
518
+
519
+ Returns pool health metrics, configuration, and fee info.
520
+
521
+ ```bash
522
+ curl -X POST http://localhost:8080/... \
523
+ -H "Content-Type: application/json" \
524
+ -d '{
525
+ "params": {
526
+ "management": {
527
+ "action": "stats",
528
+ "adminSecret": "your-secret-here"
529
+ }
530
+ }
531
+ }'
532
+ ```
533
+
534
+ **Response:**
535
+
536
+ ```json
537
+ {
538
+ "pool": {
539
+ "size": 5,
540
+ "locked": 2,
541
+ "available": 3
542
+ },
543
+ "config": {
544
+ "network": "testnet",
545
+ "lockTtlSeconds": 30,
546
+ "feeLimit": 10000,
547
+ "feeResetPeriodSeconds": 3600,
548
+ "contractCapacityRatio": 0.8,
549
+ "limitedContracts": []
550
+ },
551
+ "fees": {
552
+ "inclusionFeeDefault": 203,
553
+ "inclusionFeeLimited": 201
554
+ }
555
+ }
556
+ ```
557
+
558
+ > **Note:** If lock checks fail, `locked` and `available` will be `undefined` rather than causing the request to fail. The `size` and config info are always returned.
559
+
513
560
  **Important Notes:**
514
561
 
515
562
  - You must configure at least one channel account before the plugin can process transactions
@@ -683,6 +730,19 @@ const result = await adminClient.deleteFeeLimit('client-api-key');
683
730
  console.log(result.ok); // true
684
731
  ```
685
732
 
733
+ #### Get Pool Stats (Management)
734
+
735
+ ```typescript
736
+ // Get pool health metrics (requires adminSecret)
737
+ const stats = await adminClient.getStats();
738
+
739
+ console.log(stats.pool.size); // total channels
740
+ console.log(stats.pool.locked); // currently in-use
741
+ console.log(stats.pool.available); // size - locked
742
+ console.log(stats.config.network); // 'testnet' or 'mainnet'
743
+ console.log(stats.fees); // inclusion fee values
744
+ ```
745
+
686
746
  ### Error Handling
687
747
 
688
748
  The client provides three types of errors:
@@ -1,4 +1,4 @@
1
- import type { ChannelsClientConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse } from './types';
1
+ import type { ChannelsClientConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse, GetStatsResponse } from './types';
2
2
  /**
3
3
  * Client for interacting with the Channels plugin
4
4
  *
@@ -137,6 +137,20 @@ export declare class ChannelsClient {
137
137
  * @throws {PluginUnexpectedError} Malformed response or client-side errors
138
138
  */
139
139
  deleteFeeLimit(apiKey: string): Promise<DeleteFeeLimitResponse>;
140
+ /**
141
+ * Get pool health stats including capacity, config, and fees (requires adminSecret)
142
+ *
143
+ * @returns Pool stats with size, locked/available counts, config, and fee info
144
+ * @throws {Error} If adminSecret not provided in config
145
+ * @throws {PluginTransportError} Network/HTTP failures
146
+ * @throws {PluginExecutionError} Plugin rejected the request
147
+ * @throws {PluginUnexpectedError} Malformed response or client-side errors
148
+ *
149
+ * @example
150
+ * const stats = await client.getStats();
151
+ * console.log(stats.pool.available);
152
+ */
153
+ getStats(): Promise<GetStatsResponse>;
140
154
  /**
141
155
  * Ensures adminSecret is configured
142
156
  *
@@ -1 +1 @@
1
- {"version":3,"file":"channels-client.d.ts","sourceRoot":"","sources":["../../src/client/channels-client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EAEvB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAgB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;gBAEvB,MAAM,EAAE,oBAAoB;IAiCxC;;;;;;;;;;;;;OAaG;IACG,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAI1F;;;;;;;;;;;;;;;OAeG;IACG,wBAAwB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAItG;;;;;;;;;;;;OAYG;IACG,mBAAmB,IAAI,OAAO,CAAC,2BAA2B,CAAC;IASjE;;;;;;;;;;;;;;;OAeG;IACG,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAUnF;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAU/D;;;;;;;;;OASG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAU/D;;;;;;;;;;OAUG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAW9E;;;;;;;;;OASG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAUrE;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;;;;;OAOG;IAEH,OAAO,CAAC,eAAe;IAgBvB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAUrB;;;;;;;;OAQG;YACW,IAAI;IAwBlB;;;;;;OAMG;YACW,QAAQ;CASvB"}
1
+ {"version":3,"file":"channels-client.d.ts","sourceRoot":"","sources":["../../src/client/channels-client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAgB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;gBAEvB,MAAM,EAAE,oBAAoB;IAiCxC;;;;;;;;;;;;;OAaG;IACG,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAI1F;;;;;;;;;;;;;;;OAeG;IACG,wBAAwB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAItG;;;;;;;;;;;;OAYG;IACG,mBAAmB,IAAI,OAAO,CAAC,2BAA2B,CAAC;IASjE;;;;;;;;;;;;;;;OAeG;IACG,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAUnF;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAU/D;;;;;;;;;OASG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAU/D;;;;;;;;;;OAUG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAW9E;;;;;;;;;OASG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAUrE;;;;;;;;;;;;OAYG;IACG,QAAQ,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAS3C;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;;;;;OAOG;IAEH,OAAO,CAAC,eAAe;IAgBvB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAUrB;;;;;;;;OAQG;YACW,IAAI;IAwBlB;;;;;;OAMG;YACW,QAAQ;CASvB"}
@@ -221,6 +221,27 @@ class ChannelsClient {
221
221
  },
222
222
  });
223
223
  }
224
+ /**
225
+ * Get pool health stats including capacity, config, and fees (requires adminSecret)
226
+ *
227
+ * @returns Pool stats with size, locked/available counts, config, and fee info
228
+ * @throws {Error} If adminSecret not provided in config
229
+ * @throws {PluginTransportError} Network/HTTP failures
230
+ * @throws {PluginExecutionError} Plugin rejected the request
231
+ * @throws {PluginUnexpectedError} Malformed response or client-side errors
232
+ *
233
+ * @example
234
+ * const stats = await client.getStats();
235
+ * console.log(stats.pool.available);
236
+ */
237
+ async getStats() {
238
+ return this.call({
239
+ management: {
240
+ action: 'stats',
241
+ adminSecret: this.requireAdminSecret(),
242
+ },
243
+ });
244
+ }
224
245
  /**
225
246
  * Ensures adminSecret is configured
226
247
  *
@@ -5,6 +5,6 @@
5
5
  * in both direct HTTP mode and OpenZeppelin Relayer mode.
6
6
  */
7
7
  export { ChannelsClient } from './channels-client';
8
- export { ChannelsClientConfig, DirectHttpConfig, RelayerConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse, } from './types';
8
+ export { ChannelsClientConfig, DirectHttpConfig, RelayerConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse, GetStatsResponse, } from './types';
9
9
  export { PluginClientError, PluginTransportError, PluginExecutionError, PluginUnexpectedError } from './errors';
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC"}
@@ -156,6 +156,33 @@ export interface DeleteFeeLimitResponse {
156
156
  traces?: any[];
157
157
  };
158
158
  }
159
+ /**
160
+ * Response from getting pool stats
161
+ */
162
+ export interface GetStatsResponse {
163
+ pool: {
164
+ size: number;
165
+ locked?: number;
166
+ available?: number;
167
+ };
168
+ config: {
169
+ network: 'testnet' | 'mainnet';
170
+ lockTtlSeconds: number;
171
+ feeLimit?: number;
172
+ feeResetPeriodSeconds?: number;
173
+ contractCapacityRatio: number;
174
+ limitedContracts: string[];
175
+ };
176
+ fees: {
177
+ inclusionFeeDefault: number;
178
+ inclusionFeeLimited: number;
179
+ };
180
+ /** Optional metadata (logs and traces) */
181
+ metadata?: {
182
+ logs?: LogEntry[];
183
+ traces?: any[];
184
+ };
185
+ }
159
186
  /**
160
187
  * Plugin response structure for successful operations
161
188
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,sCAAsC;IACtC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gCAAgC;IAChC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,yBAAyB;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,oEAAoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,qBAAqB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,8 @@ export interface ChannelAccountsConfig {
13
13
  apiKeyHeader: string;
14
14
  limitedContracts: Set<string>;
15
15
  contractCapacityRatio: number;
16
+ inclusionFeeDefault: number;
17
+ inclusionFeeLimited: number;
16
18
  }
17
19
  /**
18
20
  * Load configuration from environment variables
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/plugin/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAoFD;;GAEG;AACH,wBAAgB,UAAU,IAAI,qBAAqB,CAoBlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAE3E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/plugin/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AA4FD;;GAEG;AACH,wBAAgB,UAAU,IAAI,qBAAqB,CAsBlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAE3E"}
@@ -10,6 +10,9 @@ exports.getNetworkPassphrase = getNetworkPassphrase;
10
10
  const stellar_sdk_1 = require("@stellar/stellar-sdk");
11
11
  const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
12
12
  const constants_1 = require("./constants");
13
+ // Default inclusion fees (matching launchtube)
14
+ const DEFAULT_INCLUSION_FEE_DEFAULT = Number(stellar_sdk_1.BASE_FEE) * 2 + 3; // 203
15
+ const DEFAULT_INCLUSION_FEE_LIMITED = Number(stellar_sdk_1.BASE_FEE) * 2 + 1; // 201
13
16
  function requireEnv(name) {
14
17
  const v = process.env[name];
15
18
  if (!v || v.trim() === '') {
@@ -79,6 +82,15 @@ function parseLimitedContracts() {
79
82
  }
80
83
  return new Set(contracts);
81
84
  }
85
+ function parseInclusionFee(envVar, defaultValue) {
86
+ const raw = process.env[envVar];
87
+ if (!raw)
88
+ return defaultValue;
89
+ const n = Number(raw);
90
+ if (!Number.isFinite(n) || n < 0)
91
+ return defaultValue;
92
+ return Math.floor(n);
93
+ }
82
94
  function parseContractCapacityRatio() {
83
95
  const raw = process.env.CONTRACT_CAPACITY_RATIO;
84
96
  if (!raw)
@@ -110,6 +122,8 @@ function loadConfig() {
110
122
  apiKeyHeader: parseApiKeyHeader(),
111
123
  limitedContracts: parseLimitedContracts(),
112
124
  contractCapacityRatio: parseContractCapacityRatio(),
125
+ inclusionFeeDefault: parseInclusionFee('INCLUSION_FEE_DEFAULT', DEFAULT_INCLUSION_FEE_DEFAULT),
126
+ inclusionFeeLimited: parseInclusionFee('INCLUSION_FEE_LIMITED', DEFAULT_INCLUSION_FEE_LIMITED),
113
127
  };
114
128
  }
115
129
  /**
@@ -2,13 +2,16 @@
2
2
  * fee.ts
3
3
  *
4
4
  * Static fee calculation for fee bump submissions matching launchtube.
5
- * - For Soroban transactions: use resourceFee + inclusion fee (BASE_FEE * 2 + 3)
5
+ * - For Soroban transactions: use resourceFee + inclusion fee
6
6
  * - For non-Soroban: use NON_SOROBAN_FEE + inclusion fee
7
- * - Limited contracts (from LIMITED_CONTRACTS env) get reduced fee (BASE_FEE * 2 + 1)
7
+ * - Limited contracts (from LIMITED_CONTRACTS env) get reduced fee
8
+ * - Inclusion fees are configurable via INCLUSION_FEE_DEFAULT and INCLUSION_FEE_LIMITED env vars
8
9
  */
9
10
  import { Transaction, xdr } from '@stellar/stellar-sdk';
10
- export declare const INCLUSION_FEE_DEFAULT: number;
11
- export declare const INCLUSION_FEE_LIMITED: number;
11
+ export interface InclusionFees {
12
+ inclusionFeeDefault: number;
13
+ inclusionFeeLimited: number;
14
+ }
12
15
  /**
13
16
  * Extract contract ID from a HostFunction (for func+auth flow)
14
17
  */
@@ -17,5 +20,5 @@ export declare function getContractIdFromFunc(func: xdr.HostFunction): string |
17
20
  * Extract contract ID from a Transaction (for XDR flow)
18
21
  */
19
22
  export declare function getContractIdFromTransaction(transaction: Transaction): string | undefined;
20
- export declare function calculateMaxFee(transaction: Transaction, limitedContracts?: Set<string>): number;
23
+ export declare function calculateMaxFee(transaction: Transaction, limitedContracts: Set<string>, fees: InclusionFees): number;
21
24
  //# sourceMappingURL=fee.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,WAAW,EAAE,GAAG,EAAqB,MAAM,sBAAsB,CAAC;AAGrF,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAC9D,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAE9D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,GAAG,MAAM,GAAG,SAAS,CAUhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAYzF;AASD,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,MAAM,CAoB3G"}
1
+ {"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,GAAG,EAAqB,MAAM,sBAAsB,CAAC;AAG3E,MAAM,WAAW,aAAa;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,GAAG,MAAM,GAAG,SAAS,CAUhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAYzF;AASD,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAoBpH"}
@@ -3,19 +3,17 @@
3
3
  * fee.ts
4
4
  *
5
5
  * Static fee calculation for fee bump submissions matching launchtube.
6
- * - For Soroban transactions: use resourceFee + inclusion fee (BASE_FEE * 2 + 3)
6
+ * - For Soroban transactions: use resourceFee + inclusion fee
7
7
  * - For non-Soroban: use NON_SOROBAN_FEE + inclusion fee
8
- * - Limited contracts (from LIMITED_CONTRACTS env) get reduced fee (BASE_FEE * 2 + 1)
8
+ * - Limited contracts (from LIMITED_CONTRACTS env) get reduced fee
9
+ * - Inclusion fees are configurable via INCLUSION_FEE_DEFAULT and INCLUSION_FEE_LIMITED env vars
9
10
  */
10
11
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.INCLUSION_FEE_LIMITED = exports.INCLUSION_FEE_DEFAULT = void 0;
12
12
  exports.getContractIdFromFunc = getContractIdFromFunc;
13
13
  exports.getContractIdFromTransaction = getContractIdFromTransaction;
14
14
  exports.calculateMaxFee = calculateMaxFee;
15
15
  const stellar_sdk_1 = require("@stellar/stellar-sdk");
16
16
  const constants_1 = require("./constants");
17
- exports.INCLUSION_FEE_DEFAULT = Number(stellar_sdk_1.BASE_FEE) * 2 + 3;
18
- exports.INCLUSION_FEE_LIMITED = Number(stellar_sdk_1.BASE_FEE) * 2 + 1;
19
17
  /**
20
18
  * Extract contract ID from a HostFunction (for func+auth flow)
21
19
  */
@@ -48,13 +46,13 @@ function getContractIdFromTransaction(transaction) {
48
46
  return undefined;
49
47
  }
50
48
  }
51
- function getInclusionFee(contractId, limitedContracts) {
49
+ function getInclusionFee(contractId, limitedContracts, fees) {
52
50
  if (contractId && limitedContracts.has(contractId)) {
53
- return exports.INCLUSION_FEE_LIMITED;
51
+ return fees.inclusionFeeLimited;
54
52
  }
55
- return exports.INCLUSION_FEE_DEFAULT;
53
+ return fees.inclusionFeeDefault;
56
54
  }
57
- function calculateMaxFee(transaction, limitedContracts = new Set()) {
55
+ function calculateMaxFee(transaction, limitedContracts, fees) {
58
56
  const envelope = transaction.toEnvelope();
59
57
  let resourceFee = 0n;
60
58
  if (envelope.switch() === stellar_sdk_1.xdr.EnvelopeType.envelopeTypeTx()) {
@@ -64,7 +62,7 @@ function calculateMaxFee(transaction, limitedContracts = new Set()) {
64
62
  }
65
63
  }
66
64
  const contractId = getContractIdFromTransaction(transaction);
67
- const inclusionFee = getInclusionFee(contractId, limitedContracts);
65
+ const inclusionFee = getInclusionFee(contractId, limitedContracts, fees);
68
66
  const fee = resourceFee > 0n ? resourceFee + BigInt(inclusionFee) : BigInt(constants_1.FEE.NON_SOROBAN_FEE + inclusionFee);
69
67
  console.debug(`[channels] Calculated max_fee: ${Number(fee)} stroops (resourceFee: ${resourceFee}, inclusionFee: ${inclusionFee})`);
70
68
  return Number(fee);
@@ -5,6 +5,7 @@
5
5
  * Orchestrates the transaction processing pipeline using channel accounts with fee bumping.
6
6
  */
7
7
  import { PluginContext } from '@openzeppelin/relayer-sdk';
8
+ import type { Relayer } from '@openzeppelin/relayer-sdk';
8
9
  import { Transaction, xdr } from '@stellar/stellar-sdk';
9
10
  /**
10
11
  * Extracts func and auth from an unsigned Soroban transaction.
@@ -14,6 +15,7 @@ export declare function extractFuncAuthFromUnsignedXdr(tx: Transaction): {
14
15
  func: xdr.HostFunction;
15
16
  auth: xdr.SorobanAuthorizationEntry[];
16
17
  } | null;
18
+ export declare function getAccountSequence(relayer: Relayer, address: string): Promise<string>;
17
19
  /**
18
20
  * Main plugin handler exported for OpenZeppelin Relayer
19
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/plugin/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AASvE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAWxD;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,WAAW,GACd;IAAE,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAA;CAAE,GAAG,IAAI,CAkB1E;AAsOD;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/plugin/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAa,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAQpE,OAAO,EAAW,WAAW,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAWjE;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,WAAW,GACd;IAAE,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAA;CAAE,GAAG,IAAI,CAkB1E;AAKD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsH3F;AA2PD;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
@@ -7,6 +7,7 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.extractFuncAuthFromUnsignedXdr = extractFuncAuthFromUnsignedXdr;
10
+ exports.getAccountSequence = getAccountSequence;
10
11
  exports.handler = handler;
11
12
  const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
12
13
  const pool_1 = require("./pool");
@@ -44,7 +45,121 @@ function extractFuncAuthFromUnsignedXdr(tx) {
44
45
  auth: invokeHostFn.auth(),
45
46
  };
46
47
  }
47
- async function handleXdrSubmit(xdrStr, fundRelayer, fundAddress, network, networkPassphrase, api, pool, acquireOptions, tracker) {
48
+ async function getAccountSequence(relayer, address) {
49
+ let accountKey;
50
+ try {
51
+ accountKey = stellar_sdk_1.xdr.LedgerKey.account(new stellar_sdk_1.xdr.LedgerKeyAccount({
52
+ accountId: stellar_sdk_1.Keypair.fromPublicKey(address).xdrPublicKey(),
53
+ }));
54
+ }
55
+ catch (error) {
56
+ console.error('[channels] Sequence fetch failed', {
57
+ event: 'invalid_channel_account_address',
58
+ code: 'FAILED_TO_GET_SEQUENCE',
59
+ address,
60
+ message: error instanceof Error ? error.message : String(error),
61
+ });
62
+ throw (0, relayer_sdk_1.pluginError)('Invalid channel account address', {
63
+ code: 'FAILED_TO_GET_SEQUENCE',
64
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
65
+ details: { address, message: error instanceof Error ? error.message : String(error) },
66
+ });
67
+ }
68
+ let response;
69
+ try {
70
+ response = await relayer.rpc({
71
+ jsonrpc: '2.0',
72
+ id: Math.floor(Math.random() * 1e8).toString(),
73
+ method: 'getLedgerEntries',
74
+ params: {
75
+ keys: [accountKey.toXDR('base64')],
76
+ },
77
+ });
78
+ }
79
+ catch (error) {
80
+ console.error('[channels] Sequence fetch failed', {
81
+ event: 'sequence_rpc_request_failed',
82
+ code: 'FAILED_TO_GET_SEQUENCE',
83
+ address,
84
+ message: error instanceof Error ? error.message : String(error),
85
+ });
86
+ throw (0, relayer_sdk_1.pluginError)('Failed to get sequence from channel relayer', {
87
+ code: 'FAILED_TO_GET_SEQUENCE',
88
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
89
+ details: { message: error instanceof Error ? error.message : String(error) },
90
+ });
91
+ }
92
+ if (response.error) {
93
+ console.error('[channels] Sequence fetch failed', {
94
+ event: 'sequence_rpc_error_response',
95
+ code: 'FAILED_TO_GET_SEQUENCE',
96
+ address,
97
+ message: response.error.message,
98
+ });
99
+ throw (0, relayer_sdk_1.pluginError)('Failed to get sequence from channel relayer', {
100
+ code: 'FAILED_TO_GET_SEQUENCE',
101
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
102
+ details: { message: response.error.message },
103
+ });
104
+ }
105
+ const result = response.result;
106
+ const entries = result?.entries;
107
+ if (!Array.isArray(entries)) {
108
+ console.error('[channels] Sequence fetch failed', {
109
+ event: 'sequence_rpc_invalid_result_shape',
110
+ code: 'FAILED_TO_GET_SEQUENCE',
111
+ address,
112
+ });
113
+ throw (0, relayer_sdk_1.pluginError)('Invalid RPC response for account sequence', {
114
+ code: 'FAILED_TO_GET_SEQUENCE',
115
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
116
+ details: { address },
117
+ });
118
+ }
119
+ if (!entries || entries.length === 0) {
120
+ console.warn('[channels] Sequence fetch returned no account entries', {
121
+ event: 'sequence_account_not_found',
122
+ code: 'ACCOUNT_NOT_FOUND',
123
+ address,
124
+ });
125
+ throw (0, relayer_sdk_1.pluginError)('Channel account not found on ledger', {
126
+ code: 'ACCOUNT_NOT_FOUND',
127
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
128
+ details: { address },
129
+ });
130
+ }
131
+ const firstEntryXdr = entries[0]?.xdr;
132
+ if (typeof firstEntryXdr !== 'string') {
133
+ console.error('[channels] Sequence fetch failed', {
134
+ event: 'sequence_rpc_invalid_entry_xdr',
135
+ code: 'FAILED_TO_GET_SEQUENCE',
136
+ address,
137
+ });
138
+ throw (0, relayer_sdk_1.pluginError)('Invalid RPC response for account sequence', {
139
+ code: 'FAILED_TO_GET_SEQUENCE',
140
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
141
+ details: { address },
142
+ });
143
+ }
144
+ try {
145
+ const accountEntry = stellar_sdk_1.xdr.LedgerEntryData.fromXDR(firstEntryXdr, 'base64');
146
+ return accountEntry.account().seqNum().toString();
147
+ }
148
+ catch (error) {
149
+ console.error('[channels] Sequence fetch failed', {
150
+ event: 'sequence_xdr_decode_failed',
151
+ code: 'FAILED_TO_GET_SEQUENCE',
152
+ address,
153
+ message: error instanceof Error ? error.message : String(error),
154
+ });
155
+ throw (0, relayer_sdk_1.pluginError)('Failed to decode account sequence from ledger entry', {
156
+ code: 'FAILED_TO_GET_SEQUENCE',
157
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
158
+ details: { address, message: error instanceof Error ? error.message : String(error) },
159
+ });
160
+ }
161
+ }
162
+ async function handleXdrSubmit(xdrStr, fundRelayer, fundAddress, network, networkPassphrase, api, pool, acquireOptions, fees, tracker) {
48
163
  const tx = new stellar_sdk_1.Transaction(xdrStr, networkPassphrase);
49
164
  // Unsigned XDR: extract func+auth and route through channel path
50
165
  if (tx.signatures.length === 0) {
@@ -63,14 +178,19 @@ async function handleXdrSubmit(xdrStr, fundRelayer, fundAddress, network, networ
63
178
  // Update acquireOptions with contractId from extracted func
64
179
  const contractId = (0, fee_1.getContractIdFromFunc)(extracted.func);
65
180
  const updatedOptions = { ...acquireOptions, contractId };
66
- return handleFuncAuthSubmit(extracted.func, extracted.auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, updatedOptions, tracker);
181
+ return handleFuncAuthSubmit(extracted.func, extracted.auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, updatedOptions, fees, tracker);
67
182
  }
68
183
  const validated = (0, tx_1.validateExistingTransactionForSubmitOnly)(tx);
69
- const maxFee = (0, fee_1.calculateMaxFee)(validated, acquireOptions.limitedContracts);
184
+ const maxFee = (0, fee_1.calculateMaxFee)(validated, acquireOptions.limitedContracts, fees);
185
+ const contractId = (0, fee_1.getContractIdFromTransaction)(validated);
70
186
  await tracker?.checkBudget(maxFee);
71
- return (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, validated.toXDR(), network, maxFee, api, tracker);
187
+ const submitContext = {
188
+ contractId,
189
+ isLimited: contractId ? acquireOptions.limitedContracts.has(contractId) : false,
190
+ };
191
+ return (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, validated.toXDR(), network, maxFee, api, tracker, submitContext);
72
192
  }
73
- async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, acquireOptions, tracker) {
193
+ async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, acquireOptions, fees, tracker) {
74
194
  // Simulate once — used for both read-only detection and transaction assembly
75
195
  const simulation = await (0, simulation_1.simulateTransaction)(func, auth, fundAddress, fundRelayer, networkPassphrase);
76
196
  if (simulation.isReadOnly) {
@@ -96,20 +216,25 @@ async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddr
96
216
  details: { relayerId: poolLock.relayerId },
97
217
  });
98
218
  }
99
- const channelStatus = await channelRelayer.getRelayerStatus();
100
- if (channelStatus.network_type !== 'stellar') {
219
+ if (channelInfo.network_type !== 'stellar') {
101
220
  throw (0, relayer_sdk_1.pluginError)('Channel relayer network type must be stellar', {
102
221
  code: 'UNSUPPORTED_NETWORK',
103
222
  status: constants_1.HTTP_STATUS.BAD_REQUEST,
104
- details: { network_type: channelStatus.network_type, relayerId: poolLock.relayerId },
223
+ details: { network_type: channelInfo.network_type, relayerId: poolLock.relayerId },
105
224
  });
106
225
  }
226
+ const sequence = await getAccountSequence(channelRelayer, channelInfo.address);
107
227
  // Assemble the transaction using the cached simulation result — no second RPC call
108
- const built = (0, simulation_1.buildWithChannel)(func, auth, { address: channelInfo.address, sequence: channelStatus.sequence_number }, networkPassphrase, simulation.rawSimResult);
228
+ const built = (0, simulation_1.buildWithChannel)(func, auth, { address: channelInfo.address, sequence }, networkPassphrase, simulation.rawSimResult);
109
229
  const signedTx = await (0, submit_1.signWithChannelAndFund)(built, channelRelayer, fundRelayer, channelInfo.address, fundAddress, networkPassphrase);
110
- const maxFee = (0, fee_1.calculateMaxFee)(signedTx, acquireOptions.limitedContracts);
230
+ const maxFee = (0, fee_1.calculateMaxFee)(signedTx, acquireOptions.limitedContracts, fees);
231
+ const contractId = (0, fee_1.getContractIdFromFunc)(func);
111
232
  await tracker?.checkBudget(maxFee);
112
- return await (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, signedTx.toXDR(), network, maxFee, api, tracker);
233
+ const submitContext = {
234
+ contractId,
235
+ isLimited: contractId ? acquireOptions.limitedContracts.has(contractId) : false,
236
+ };
237
+ return await (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, signedTx.toXDR(), network, maxFee, api, tracker, submitContext);
113
238
  }
114
239
  finally {
115
240
  if (poolLock) {
@@ -172,16 +297,20 @@ async function channelAccounts(context) {
172
297
  limitedContracts: config.limitedContracts,
173
298
  capacityRatio: config.contractCapacityRatio,
174
299
  };
300
+ const fees = {
301
+ inclusionFeeDefault: config.inclusionFeeDefault,
302
+ inclusionFeeLimited: config.inclusionFeeLimited,
303
+ };
175
304
  // 4. Branch by request type
176
305
  if (request.type === 'xdr') {
177
306
  console.log(`[channels] Flow: XDR submit-only`);
178
- return await handleXdrSubmit(request.xdr, fundRelayer, fundInfo.address, config.network, networkPassphrase, api, pool, acquireOptions, tracker);
307
+ return await handleXdrSubmit(request.xdr, fundRelayer, fundInfo.address, config.network, networkPassphrase, api, pool, acquireOptions, fees, tracker);
179
308
  }
180
309
  // Extract contractId for func+auth flow
181
310
  const contractId = (0, fee_1.getContractIdFromFunc)(request.func);
182
311
  const funcAcquireOptions = { ...acquireOptions, contractId };
183
312
  console.log(`[channels] Flow: func+auth with channel account`);
184
- return await handleFuncAuthSubmit(request.func, request.auth, api, pool, fundRelayer, fundInfo.address, config.network, networkPassphrase, funcAcquireOptions, tracker);
313
+ return await handleFuncAuthSubmit(request.func, request.auth, api, pool, fundRelayer, fundInfo.address, config.network, networkPassphrase, funcAcquireOptions, fees, tracker);
185
314
  }
186
315
  /**
187
316
  * Main plugin handler exported for OpenZeppelin Relayer
@@ -1 +1 @@
1
- {"version":3,"file":"management.d.ts","sourceRoot":"","sources":["../../src/plugin/management.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,2BAA2B,CAAC;AAM9E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAIxD;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAkC3E"}
1
+ {"version":3,"file":"management.d.ts","sourceRoot":"","sources":["../../src/plugin/management.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,2BAA2B,CAAC;AAO9E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAIxD;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAoC3E"}
@@ -48,6 +48,8 @@ async function handleManagement(context) {
48
48
  return await setFeeLimit(kv, config.network, m);
49
49
  case 'deleteFeeLimit':
50
50
  return await deleteFeeLimit(kv, config.network, m);
51
+ case 'stats':
52
+ return await getPoolStats(config, kv);
51
53
  default:
52
54
  throw (0, relayer_sdk_1.pluginError)('Invalid management action', { code: 'INVALID_ACTION', status: constants_1.HTTP_STATUS.BAD_REQUEST });
53
55
  }
@@ -194,6 +196,52 @@ async function setChannelAccounts(kv, network, payload) {
194
196
  });
195
197
  }
196
198
  }
199
+ async function getPoolStats(config, kv) {
200
+ const { network } = config;
201
+ // 1. Get relayer IDs (1 KV call)
202
+ const key = `${network}:channel:relayer-ids`;
203
+ let relayerIds;
204
+ try {
205
+ const doc = await kv.get?.(key);
206
+ relayerIds = Array.isArray(doc?.relayerIds) ? doc.relayerIds : [];
207
+ }
208
+ catch (e) {
209
+ throw (0, relayer_sdk_1.pluginError)('KV error while reading pool stats', {
210
+ code: 'KV_ERROR',
211
+ status: constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR,
212
+ });
213
+ }
214
+ // 2. Check all locks in parallel (N KV calls) — best-effort
215
+ let locked;
216
+ let available;
217
+ try {
218
+ const results = await Promise.all(relayerIds.map((id) => kv.exists(`${network}:channel:in-use:${id}`)));
219
+ locked = results.filter(Boolean).length;
220
+ available = relayerIds.length - locked;
221
+ }
222
+ catch {
223
+ // Non-fatal: return pool size without lock info
224
+ }
225
+ return {
226
+ pool: {
227
+ size: relayerIds.length,
228
+ locked,
229
+ available,
230
+ },
231
+ config: {
232
+ network,
233
+ lockTtlSeconds: config.lockTtlSeconds,
234
+ feeLimit: config.feeLimit,
235
+ feeResetPeriodSeconds: config.feeResetPeriodMs ? config.feeResetPeriodMs / 1000 : undefined,
236
+ contractCapacityRatio: config.contractCapacityRatio,
237
+ limitedContracts: Array.from(config.limitedContracts),
238
+ },
239
+ fees: {
240
+ inclusionFeeDefault: config.inclusionFeeDefault,
241
+ inclusionFeeLimited: config.inclusionFeeLimited,
242
+ },
243
+ };
244
+ }
197
245
  function normalizeId(id) {
198
246
  return String(id).trim().toLowerCase();
199
247
  }
@@ -32,5 +32,6 @@ export declare class ChannelPool {
32
32
  private membershipKey;
33
33
  private lockKey;
34
34
  private getRelayerIdsFromKV;
35
+ private getPoolCapacityDetails;
35
36
  }
36
37
  //# sourceMappingURL=pool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/plugin/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AAIvE,MAAM,MAAM,QAAQ,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAIF,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;gBAEvB,OAAO,EAAE,SAAS,GAAG,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM;IAQrF,4CAA4C;IACtC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;YA4B3C,eAAe;YAKf,iBAAiB;IA2B/B,oCAAoC;IAC9B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,OAAO;YAID,mBAAmB;CAWlC"}
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/plugin/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AAIvE,MAAM,MAAM,QAAQ,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAUF,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;gBAEvB,OAAO,EAAE,SAAS,GAAG,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM;IAQrF,4CAA4C;IACtC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;YAyB3C,eAAe;YAKf,iBAAiB;IA2B/B,oCAAoC;IAC9B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,OAAO;YAID,mBAAmB;YAYnB,sBAAsB;CAUrC"}
@@ -26,7 +26,6 @@ class ChannelPool {
26
26
  /** Acquire a relayerId with a token lock */
27
27
  async acquire(options) {
28
28
  const maxSpins = constants_1.POOL.MUTEX_MAX_SPINS;
29
- const isLimited = options.contractId && options.limitedContracts.has(options.contractId);
30
29
  for (let i = 0; i < maxSpins; i++) {
31
30
  const r = await this.withGlobalMutex(() => this.tryLockAnyRelayer(options));
32
31
  if (r === null) {
@@ -36,12 +35,12 @@ class ChannelPool {
36
35
  }
37
36
  return r;
38
37
  }
39
- if (isLimited) {
40
- console.log(`[channels] Contract ${options.contractId} limited to ${Math.round(options.capacityRatio * 100)}% capacity - no channels available`);
41
- }
38
+ const diagnostics = await this.getPoolCapacityDetails(options, maxSpins);
39
+ console.warn('[channels] Pool capacity exhausted', diagnostics);
42
40
  throw (0, relayer_sdk_1.pluginError)('Too many transactions queued. Please try again later', {
43
41
  code: 'POOL_CAPACITY',
44
42
  status: constants_1.HTTP_STATUS.SERVICE_UNAVAILABLE,
43
+ details: diagnostics,
45
44
  });
46
45
  }
47
46
  // Run a function under the short-lived global mutex; returns null if busy
@@ -106,6 +105,15 @@ class ChannelPool {
106
105
  return [];
107
106
  }
108
107
  }
108
+ async getPoolCapacityDetails(options, maxSpins) {
109
+ const isLimited = !!(options.contractId && options.limitedContracts.has(options.contractId));
110
+ return {
111
+ reason: isLimited ? 'limited_contract_capacity' : 'all_channels_busy_or_mutex_contention',
112
+ contractId: options.contractId,
113
+ capacityRatio: options.capacityRatio,
114
+ maxSpins,
115
+ };
116
+ }
109
117
  }
110
118
  exports.ChannelPool = ChannelPool;
111
119
  function shuffle(arr) {
@@ -7,6 +7,10 @@ import { Transaction } from '@stellar/stellar-sdk';
7
7
  import { Relayer, PluginAPI } from '@openzeppelin/relayer-sdk';
8
8
  import { ChannelAccountsResponse } from './types';
9
9
  import { FeeTracker } from './fee-tracking';
10
+ export interface SubmitContext {
11
+ contractId?: string;
12
+ isLimited?: boolean;
13
+ }
10
14
  /**
11
15
  * Sign transaction with both channel and fund relayers
12
16
  * - First sign with channel account
@@ -17,7 +21,12 @@ export declare function signWithChannelAndFund(transaction: Transaction, channel
17
21
  /**
18
22
  * Submit transaction with fee bump and wait for confirmation
19
23
  */
20
- export declare function submitWithFeeBumpAndWait(fundRelayer: Relayer, signedXdr: string, network: 'testnet' | 'mainnet', maxFee: number, api: PluginAPI, tracker?: FeeTracker): Promise<ChannelAccountsResponse>;
24
+ export declare function submitWithFeeBumpAndWait(fundRelayer: Relayer, signedXdr: string, network: 'testnet' | 'mainnet', maxFee: number, api: PluginAPI, tracker?: FeeTracker, context?: SubmitContext): Promise<ChannelAccountsResponse>;
25
+ /** Try to decode a transaction result XDR from the reason string */
26
+ export declare function decodeTransactionResult(reason: string): {
27
+ feeCharged: number;
28
+ resultCode: string;
29
+ } | null;
21
30
  /** Strip provider wrapper text, extract last segment (e.g., "TxInsufficientBalance") */
22
31
  export declare function sanitizeReason(reason: string): string;
23
32
  //# sourceMappingURL=submit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/plugin/submit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAEL,OAAO,EAGP,SAAS,EACV,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,uBAAuB,CAAC,CAkElC;AASD,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOrD"}
1
+ {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/plugin/submit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAEL,OAAO,EAGP,SAAS,EACV,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,uBAAuB,CAAC,CA6ElC;AASD,oEAAoE;AACpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAwBzG;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOrD"}
@@ -7,6 +7,7 @@
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.signWithChannelAndFund = signWithChannelAndFund;
9
9
  exports.submitWithFeeBumpAndWait = submitWithFeeBumpAndWait;
10
+ exports.decodeTransactionResult = decodeTransactionResult;
10
11
  exports.sanitizeReason = sanitizeReason;
11
12
  const stellar_sdk_1 = require("@stellar/stellar-sdk");
12
13
  const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
@@ -40,7 +41,7 @@ async function signWithChannelAndFund(transaction, channelRelayer, _fundRelayer,
40
41
  /**
41
42
  * Submit transaction with fee bump and wait for confirmation
42
43
  */
43
- async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee, api, tracker) {
44
+ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee, api, tracker, context) {
44
45
  // Submit with fee bump
45
46
  console.debug(`[channels] Sending fee bump tx: network=${network}, maxFee=${maxFee}, xdr_len=${signedXdr.length}`);
46
47
  const payload = {
@@ -64,7 +65,18 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
64
65
  await tracker.recordUsage(maxFee);
65
66
  }
66
67
  const rawReason = final.status_reason || 'Transaction failed';
67
- console.error(`[channels] Transaction failed: ${rawReason}`);
68
+ const decoded = decodeTransactionResult(rawReason);
69
+ const contractType = context?.isLimited ? 'limited' : 'default';
70
+ const base = `[channels] Transaction failed: contractId=${context?.contractId ?? 'unknown'}, contractType=${contractType}, maxFee=${maxFee}`;
71
+ if (decoded?.resultCode === 'txInsufficientFee') {
72
+ console.error(`${base}, reason=txInsufficientFee, requiredFee=${decoded.feeCharged}, shortfall=${decoded.feeCharged - maxFee}`);
73
+ }
74
+ else if (decoded) {
75
+ console.error(`${base}, reason=${decoded.resultCode}`);
76
+ }
77
+ else {
78
+ console.error(`${base}, reason=${rawReason}`);
79
+ }
68
80
  const reason = sanitizeReason(rawReason);
69
81
  throw (0, relayer_sdk_1.pluginError)(reason, {
70
82
  code: 'ONCHAIN_FAILED',
@@ -109,6 +121,33 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
109
121
  function isSignTransactionResponseStellar(data) {
110
122
  return data !== null && typeof data === 'object' && 'signature' in data && 'signedXdr' in data;
111
123
  }
124
+ /** Try to decode a transaction result XDR from the reason string */
125
+ function decodeTransactionResult(reason) {
126
+ try {
127
+ const match = reason.match(/([A-Za-z0-9+/=]{20,})$/);
128
+ if (!match)
129
+ return null;
130
+ const result = stellar_sdk_1.xdr.TransactionResult.fromXDR(match[1], 'base64');
131
+ let resultCode = result.result().switch().name;
132
+ // Unwrap fee bump inner failure to get the actual result code
133
+ if (resultCode === 'txFeeBumpInnerFailed') {
134
+ try {
135
+ const innerResult = result.result().innerResultPair().result();
136
+ resultCode = innerResult.result().switch().name;
137
+ }
138
+ catch {
139
+ // keep outer result code if unwrap fails
140
+ }
141
+ }
142
+ return {
143
+ feeCharged: Number(result.feeCharged().toBigInt()),
144
+ resultCode,
145
+ };
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
112
151
  /** Strip provider wrapper text, extract last segment (e.g., "TxInsufficientBalance") */
113
152
  function sanitizeReason(reason) {
114
153
  const segments = reason.split(/:\s*/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/relayer-plugin-channels",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "OpenZeppelin Relayer Plugin for Stellar Channel Accounts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",