@openzeppelin/relayer-plugin-channels 0.5.0 → 0.7.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 +79 -43
- package/dist/client/channels-client.d.ts +1 -1
- package/dist/client/channels-client.d.ts.map +1 -1
- package/dist/client/channels-client.js +20 -21
- package/dist/client/errors.d.ts +1 -1
- package/dist/client/errors.js +12 -9
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/types.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/plugin/build.d.ts +1 -1
- package/dist/plugin/build.d.ts.map +1 -1
- package/dist/plugin/build.js +11 -17
- package/dist/plugin/config.d.ts +4 -2
- package/dist/plugin/config.d.ts.map +1 -1
- package/dist/plugin/config.js +43 -13
- package/dist/plugin/constants.d.ts +1 -2
- package/dist/plugin/constants.d.ts.map +1 -1
- package/dist/plugin/constants.js +2 -3
- package/dist/plugin/fee-tracking.d.ts +2 -2
- package/dist/plugin/fee-tracking.d.ts.map +1 -1
- package/dist/plugin/fee-tracking.js +4 -9
- package/dist/plugin/fee.d.ts +16 -5
- package/dist/plugin/fee.d.ts.map +1 -1
- package/dist/plugin/fee.js +52 -14
- package/dist/plugin/handler.d.ts +10 -1
- package/dist/plugin/handler.d.ts.map +1 -1
- package/dist/plugin/handler.js +87 -52
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/management.d.ts +1 -1
- package/dist/plugin/management.d.ts.map +1 -1
- package/dist/plugin/management.js +53 -79
- package/dist/plugin/pool.d.ts +8 -3
- package/dist/plugin/pool.d.ts.map +1 -1
- package/dist/plugin/pool.js +45 -17
- package/dist/plugin/simulation.d.ts +4 -2
- package/dist/plugin/simulation.d.ts.map +1 -1
- package/dist/plugin/simulation.js +41 -33
- package/dist/plugin/submit.d.ts +7 -5
- package/dist/plugin/submit.d.ts.map +1 -1
- package/dist/plugin/submit.js +23 -13
- package/dist/plugin/tx.d.ts +1 -1
- package/dist/plugin/tx.d.ts.map +1 -1
- package/dist/plugin/tx.js +6 -8
- package/dist/plugin/types.d.ts +4 -4
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/validation.d.ts +1 -1
- package/dist/plugin/validation.d.ts.map +1 -1
- package/dist/plugin/validation.js +26 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ A plugin for OpenZeppelin Relayer that enables parallel transaction submission o
|
|
|
16
16
|
- [Development](#development)
|
|
17
17
|
- [Overview](#overview)
|
|
18
18
|
- [Architecture](#architecture)
|
|
19
|
+
- [Contract Capacity Limits](#contract-capacity-limits)
|
|
19
20
|
- [Management API](#management-api)
|
|
20
21
|
- [List Channel Accounts](#list-channel-accounts)
|
|
21
22
|
- [Set Channel Accounts](#set-channel-accounts)
|
|
@@ -109,7 +110,7 @@ mkdir -p plugins/channels
|
|
|
109
110
|
`plugins/channels/index.ts`
|
|
110
111
|
|
|
111
112
|
```ts
|
|
112
|
-
export { handler } from
|
|
113
|
+
export { handler } from '@openzeppelin/relayer-plugin-channels';
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
### Configure the Relayer
|
|
@@ -224,6 +225,10 @@ export LOCK_TTL_SECONDS=10 # default: 30, min: 3, max: 30
|
|
|
224
225
|
export FEE_LIMIT=1000000 # Default max fee per API key in stroops (disabled if not set)
|
|
225
226
|
export FEE_RESET_PERIOD_SECONDS=86400 # Reset fee consumption every N seconds (e.g., 86400 = 24 hours)
|
|
226
227
|
export API_KEY_HEADER="x-api-key" # Header name to extract API key (default: x-api-key)
|
|
228
|
+
|
|
229
|
+
# Contract capacity limits (optional)
|
|
230
|
+
export LIMITED_CONTRACTS="CDL74RF5BLYR2YBLCCI7F5FB6TPSCLKEJUBSD2RSVWZ4YHF3VMFAIGWA" # Comma-separated contract addresses
|
|
231
|
+
export CONTRACT_CAPACITY_RATIO=0.8 # Max ratio of pool for limited contracts (default: 0.8 = 80%)
|
|
227
232
|
```
|
|
228
233
|
|
|
229
234
|
Your Relayer should now contain:
|
|
@@ -291,6 +296,41 @@ The Channels plugin accepts Soroban operations and handles all the complexity of
|
|
|
291
296
|
- **Channel Accounts**: Provide unique sequence numbers for parallel transaction submission
|
|
292
297
|
- The channel account is the transaction source and signer; the fund account wraps it in a fee bump
|
|
293
298
|
|
|
299
|
+
## Contract Capacity Limits
|
|
300
|
+
|
|
301
|
+
High-volume contracts can monopolize the channel pool, starving other traffic. Contract capacity limits allow you to reserve a portion of the pool for non-limited contracts.
|
|
302
|
+
|
|
303
|
+
### Configuration
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Comma-separated list of contract addresses to limit (case-insensitive)
|
|
307
|
+
export LIMITED_CONTRACTS="CDL74RF5BLYR2YBLCCI7F5FB6TPSCLKEJUBSD2RSVWZ4YHF3VMFAIGWA,CABC123..."
|
|
308
|
+
|
|
309
|
+
# Maximum ratio of pool that limited contracts can use (default: 0.8)
|
|
310
|
+
export CONTRACT_CAPACITY_RATIO=0.8
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### How It Works
|
|
314
|
+
|
|
315
|
+
- **Limited contracts** can only acquire from a deterministic subset of channels (e.g., 80% of pool)
|
|
316
|
+
- **Unlisted contracts** have full access to all channels
|
|
317
|
+
- The subset is selected deterministically using a hash-based partition, ensuring stable channel assignment
|
|
318
|
+
- Minimum 1 channel is always guaranteed, even at very low ratios
|
|
319
|
+
|
|
320
|
+
### Example
|
|
321
|
+
|
|
322
|
+
With 10 channel accounts and `CONTRACT_CAPACITY_RATIO=0.8`:
|
|
323
|
+
|
|
324
|
+
- Limited contracts can use up to 8 channels (`floor(10 * 0.8)`)
|
|
325
|
+
- 2 channels are always reserved for non-limited traffic
|
|
326
|
+
- If all 8 limited slots are in use, limited contracts get `POOL_CAPACITY` error while non-limited contracts can still acquire
|
|
327
|
+
|
|
328
|
+
### Notes
|
|
329
|
+
|
|
330
|
+
- Contract IDs are matched case-insensitively (normalized to uppercase internally)
|
|
331
|
+
- If contract ID cannot be extracted from the request (non-`invokeContract` operations), no limit is applied
|
|
332
|
+
- Zero runtime overhead for unlimited contracts
|
|
333
|
+
|
|
294
334
|
## Management API
|
|
295
335
|
|
|
296
336
|
The Channels plugin provides a management API to dynamically configure channel accounts. This API requires authentication via the `PLUGIN_ADMIN_SECRET` environment variable.
|
|
@@ -492,20 +532,20 @@ pnpm add @openzeppelin/relayer-plugin-channels
|
|
|
492
532
|
### Quick Start
|
|
493
533
|
|
|
494
534
|
```typescript
|
|
495
|
-
import { ChannelsClient } from
|
|
535
|
+
import { ChannelsClient } from '@openzeppelin/relayer-plugin-channels';
|
|
496
536
|
|
|
497
537
|
// Connecting to OpenZeppelin's managed Channels service
|
|
498
538
|
const client = new ChannelsClient({
|
|
499
|
-
baseUrl:
|
|
500
|
-
apiKey:
|
|
539
|
+
baseUrl: 'https://channels.openzeppelin.com',
|
|
540
|
+
apiKey: 'your-api-key',
|
|
501
541
|
});
|
|
502
542
|
|
|
503
543
|
// Connecting to your own Relayer with Channels plugin
|
|
504
544
|
const relayerClient = new ChannelsClient({
|
|
505
|
-
baseUrl:
|
|
506
|
-
pluginId:
|
|
507
|
-
apiKey:
|
|
508
|
-
adminSecret:
|
|
545
|
+
baseUrl: 'http://localhost:8080',
|
|
546
|
+
pluginId: 'channels',
|
|
547
|
+
apiKey: 'your-relayer-api-key',
|
|
548
|
+
adminSecret: 'your-admin-secret', // Optional: Required for management operations
|
|
509
549
|
});
|
|
510
550
|
```
|
|
511
551
|
|
|
@@ -518,14 +558,14 @@ When connecting to OpenZeppelin's managed Channels service (which runs behind Cl
|
|
|
518
558
|
```typescript
|
|
519
559
|
// Mainnet
|
|
520
560
|
const client = new ChannelsClient({
|
|
521
|
-
baseUrl:
|
|
522
|
-
apiKey:
|
|
561
|
+
baseUrl: 'https://channels.openzeppelin.com',
|
|
562
|
+
apiKey: 'your-api-key',
|
|
523
563
|
});
|
|
524
564
|
|
|
525
565
|
// Testnet
|
|
526
566
|
const testnetClient = new ChannelsClient({
|
|
527
|
-
baseUrl:
|
|
528
|
-
apiKey:
|
|
567
|
+
baseUrl: 'https://channels.openzeppelin.com/testnet',
|
|
568
|
+
apiKey: 'your-api-key',
|
|
529
569
|
});
|
|
530
570
|
```
|
|
531
571
|
|
|
@@ -540,10 +580,10 @@ When connecting directly to your own OpenZeppelin Relayer instance, include the
|
|
|
540
580
|
|
|
541
581
|
```typescript
|
|
542
582
|
const client = new ChannelsClient({
|
|
543
|
-
baseUrl:
|
|
544
|
-
pluginId:
|
|
545
|
-
apiKey:
|
|
546
|
-
adminSecret:
|
|
583
|
+
baseUrl: 'http://localhost:8080',
|
|
584
|
+
pluginId: 'channels',
|
|
585
|
+
apiKey: 'your-relayer-api-key',
|
|
586
|
+
adminSecret: 'your-admin-secret', // Optional: Required for management operations
|
|
547
587
|
});
|
|
548
588
|
```
|
|
549
589
|
|
|
@@ -556,7 +596,7 @@ The client automatically routes requests appropriately based on whether `pluginI
|
|
|
556
596
|
```typescript
|
|
557
597
|
// Submit a complete, signed transaction
|
|
558
598
|
const result = await client.submitTransaction({
|
|
559
|
-
xdr:
|
|
599
|
+
xdr: 'AAAAAgAAAAC...', // Complete transaction envelope XDR
|
|
560
600
|
});
|
|
561
601
|
|
|
562
602
|
console.log(result.hash); // Transaction hash
|
|
@@ -569,8 +609,8 @@ console.log(result.transactionId); // Relayer transaction ID
|
|
|
569
609
|
```typescript
|
|
570
610
|
// Submit func+auth (uses channel accounts and simulation)
|
|
571
611
|
const result = await client.submitSorobanTransaction({
|
|
572
|
-
func:
|
|
573
|
-
auth: [
|
|
612
|
+
func: 'AAAABAAAAAEAAAAGc3ltYm9s...', // Host function XDR (base64)
|
|
613
|
+
auth: ['AAAACAAAAAEAAAA...'], // Auth entry XDRs (base64)
|
|
574
614
|
});
|
|
575
615
|
|
|
576
616
|
console.log(result.hash);
|
|
@@ -581,10 +621,10 @@ console.log(result.hash);
|
|
|
581
621
|
```typescript
|
|
582
622
|
// Initialize client with admin secret
|
|
583
623
|
const adminClient = new ChannelsClient({
|
|
584
|
-
baseUrl:
|
|
585
|
-
apiKey:
|
|
586
|
-
pluginId:
|
|
587
|
-
adminSecret:
|
|
624
|
+
baseUrl: 'http://localhost:8080',
|
|
625
|
+
apiKey: 'your-api-key',
|
|
626
|
+
pluginId: 'channels',
|
|
627
|
+
adminSecret: 'your-admin-secret', // Required for management operations
|
|
588
628
|
});
|
|
589
629
|
|
|
590
630
|
// List configured channel accounts
|
|
@@ -596,11 +636,7 @@ console.log(accounts.relayerIds); // ['channel-001', 'channel-002', ...]
|
|
|
596
636
|
|
|
597
637
|
```typescript
|
|
598
638
|
// Configure channel accounts (requires adminSecret)
|
|
599
|
-
const result = await adminClient.setChannelAccounts([
|
|
600
|
-
"channel-001",
|
|
601
|
-
"channel-002",
|
|
602
|
-
"channel-003",
|
|
603
|
-
]);
|
|
639
|
+
const result = await adminClient.setChannelAccounts(['channel-001', 'channel-002', 'channel-003']);
|
|
604
640
|
|
|
605
641
|
console.log(result.ok); // true
|
|
606
642
|
console.log(result.appliedRelayerIds); // ['channel-001', 'channel-002', 'channel-003']
|
|
@@ -610,7 +646,7 @@ console.log(result.appliedRelayerIds); // ['channel-001', 'channel-002', 'channe
|
|
|
610
646
|
|
|
611
647
|
```typescript
|
|
612
648
|
// Query fee consumption for an API key (requires adminSecret)
|
|
613
|
-
const usage = await adminClient.getFeeUsage(
|
|
649
|
+
const usage = await adminClient.getFeeUsage('client-api-key');
|
|
614
650
|
|
|
615
651
|
console.log(usage.consumed); // 500000 (stroops)
|
|
616
652
|
console.log(usage.limit); // 1000000 (effective limit)
|
|
@@ -623,7 +659,7 @@ console.log(usage.periodEndsAt); // '2024-01-16T00:00:00.000Z'
|
|
|
623
659
|
|
|
624
660
|
```typescript
|
|
625
661
|
// Query fee limit configuration for an API key (requires adminSecret)
|
|
626
|
-
const limitInfo = await adminClient.getFeeLimit(
|
|
662
|
+
const limitInfo = await adminClient.getFeeLimit('client-api-key');
|
|
627
663
|
|
|
628
664
|
console.log(limitInfo.limit); // 500000 (custom limit if set, otherwise default)
|
|
629
665
|
```
|
|
@@ -632,7 +668,7 @@ console.log(limitInfo.limit); // 500000 (custom limit if set, otherwise default)
|
|
|
632
668
|
|
|
633
669
|
```typescript
|
|
634
670
|
// Set a custom fee limit for an API key (requires adminSecret)
|
|
635
|
-
const result = await adminClient.setFeeLimit(
|
|
671
|
+
const result = await adminClient.setFeeLimit('client-api-key', 500000);
|
|
636
672
|
|
|
637
673
|
console.log(result.ok); // true
|
|
638
674
|
console.log(result.limit); // 500000
|
|
@@ -642,7 +678,7 @@ console.log(result.limit); // 500000
|
|
|
642
678
|
|
|
643
679
|
```typescript
|
|
644
680
|
// Remove custom fee limit, revert to default (requires adminSecret)
|
|
645
|
-
const result = await adminClient.deleteFeeLimit(
|
|
681
|
+
const result = await adminClient.deleteFeeLimit('client-api-key');
|
|
646
682
|
|
|
647
683
|
console.log(result.ok); // true
|
|
648
684
|
```
|
|
@@ -656,22 +692,22 @@ import {
|
|
|
656
692
|
PluginTransportError,
|
|
657
693
|
PluginExecutionError,
|
|
658
694
|
PluginUnexpectedError,
|
|
659
|
-
} from
|
|
695
|
+
} from '@openzeppelin/relayer-plugin-channels';
|
|
660
696
|
|
|
661
697
|
try {
|
|
662
|
-
const result = await client.submitTransaction({ xdr:
|
|
698
|
+
const result = await client.submitTransaction({ xdr: '...' });
|
|
663
699
|
} catch (error) {
|
|
664
700
|
if (error instanceof PluginTransportError) {
|
|
665
701
|
// Network/HTTP failures (connection refused, timeout, 500/502/503)
|
|
666
|
-
console.error(
|
|
667
|
-
console.error(
|
|
702
|
+
console.error('Transport error:', error.message);
|
|
703
|
+
console.error('Status code:', error.statusCode);
|
|
668
704
|
} else if (error instanceof PluginExecutionError) {
|
|
669
705
|
// Plugin rejected the request (validation, business logic, on-chain failure)
|
|
670
|
-
console.error(
|
|
671
|
-
console.error(
|
|
706
|
+
console.error('Execution error:', error.message);
|
|
707
|
+
console.error('Details:', error.errorDetails);
|
|
672
708
|
} else if (error instanceof PluginUnexpectedError) {
|
|
673
709
|
// Client-side parsing/validation errors
|
|
674
|
-
console.error(
|
|
710
|
+
console.error('Unexpected error:', error.message);
|
|
675
711
|
}
|
|
676
712
|
}
|
|
677
713
|
```
|
|
@@ -681,12 +717,12 @@ try {
|
|
|
681
717
|
Responses include optional metadata (logs and traces) when the plugin is configured with `emit_logs` and `emit_traces`:
|
|
682
718
|
|
|
683
719
|
```typescript
|
|
684
|
-
const result = await client.submitTransaction({ xdr:
|
|
720
|
+
const result = await client.submitTransaction({ xdr: '...' });
|
|
685
721
|
|
|
686
722
|
// Access metadata if available
|
|
687
723
|
if (result.metadata) {
|
|
688
|
-
console.log(
|
|
689
|
-
console.log(
|
|
724
|
+
console.log('Logs:', result.metadata.logs);
|
|
725
|
+
console.log('Traces:', result.metadata.traces);
|
|
690
726
|
}
|
|
691
727
|
```
|
|
692
728
|
|
|
@@ -705,7 +741,7 @@ import type {
|
|
|
705
741
|
GetFeeLimitResponse,
|
|
706
742
|
SetFeeLimitResponse,
|
|
707
743
|
DeleteFeeLimitResponse,
|
|
708
|
-
} from
|
|
744
|
+
} from '@openzeppelin/relayer-plugin-channels';
|
|
709
745
|
```
|
|
710
746
|
|
|
711
747
|
### Configuration Options
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelsClientConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse } from
|
|
1
|
+
import type { ChannelsClientConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Client for interacting with the Channels plugin
|
|
4
4
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channels-client.d.ts","sourceRoot":"","sources":["../../src/client/channels-client.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -31,9 +31,9 @@ class ChannelsClient {
|
|
|
31
31
|
constructor(config) {
|
|
32
32
|
this.adminSecret = config.adminSecret;
|
|
33
33
|
// Route through Relayer plugin system if pluginId provided, otherwise connect directly
|
|
34
|
-
if (
|
|
34
|
+
if ('pluginId' in config && config.pluginId) {
|
|
35
35
|
this.pluginId = config.pluginId;
|
|
36
|
-
const apiKeyHeader = config.apiKeyHeader ||
|
|
36
|
+
const apiKeyHeader = config.apiKeyHeader || 'x-api-key';
|
|
37
37
|
const relayerConfig = new relayer_sdk_1.Configuration({
|
|
38
38
|
basePath: config.baseUrl,
|
|
39
39
|
accessToken: config.apiKey,
|
|
@@ -44,14 +44,14 @@ class ChannelsClient {
|
|
|
44
44
|
this.pluginsApi = new relayer_sdk_1.PluginsApi(relayerConfig);
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
|
-
if (!(
|
|
48
|
-
throw new Error(
|
|
47
|
+
if (!('baseUrl' in config) || !config.baseUrl) {
|
|
48
|
+
throw new Error('baseUrl is required when pluginId is not provided');
|
|
49
49
|
}
|
|
50
50
|
this.axiosClient = axios_1.default.create({
|
|
51
51
|
baseURL: config.baseUrl,
|
|
52
52
|
timeout: config.timeout || 30000,
|
|
53
53
|
headers: {
|
|
54
|
-
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
55
|
Authorization: `Bearer ${config.apiKey}`,
|
|
56
56
|
},
|
|
57
57
|
});
|
|
@@ -109,7 +109,7 @@ class ChannelsClient {
|
|
|
109
109
|
async listChannelAccounts() {
|
|
110
110
|
return this.call({
|
|
111
111
|
management: {
|
|
112
|
-
action:
|
|
112
|
+
action: 'listChannelAccounts',
|
|
113
113
|
adminSecret: this.requireAdminSecret(),
|
|
114
114
|
},
|
|
115
115
|
});
|
|
@@ -133,7 +133,7 @@ class ChannelsClient {
|
|
|
133
133
|
async setChannelAccounts(relayerIds) {
|
|
134
134
|
return this.call({
|
|
135
135
|
management: {
|
|
136
|
-
action:
|
|
136
|
+
action: 'setChannelAccounts',
|
|
137
137
|
adminSecret: this.requireAdminSecret(),
|
|
138
138
|
relayerIds,
|
|
139
139
|
},
|
|
@@ -156,7 +156,7 @@ class ChannelsClient {
|
|
|
156
156
|
async getFeeUsage(apiKey) {
|
|
157
157
|
return this.call({
|
|
158
158
|
management: {
|
|
159
|
-
action:
|
|
159
|
+
action: 'getFeeUsage',
|
|
160
160
|
adminSecret: this.requireAdminSecret(),
|
|
161
161
|
apiKey,
|
|
162
162
|
},
|
|
@@ -175,7 +175,7 @@ class ChannelsClient {
|
|
|
175
175
|
async getFeeLimit(apiKey) {
|
|
176
176
|
return this.call({
|
|
177
177
|
management: {
|
|
178
|
-
action:
|
|
178
|
+
action: 'getFeeLimit',
|
|
179
179
|
adminSecret: this.requireAdminSecret(),
|
|
180
180
|
apiKey,
|
|
181
181
|
},
|
|
@@ -195,7 +195,7 @@ class ChannelsClient {
|
|
|
195
195
|
async setFeeLimit(apiKey, limit) {
|
|
196
196
|
return this.call({
|
|
197
197
|
management: {
|
|
198
|
-
action:
|
|
198
|
+
action: 'setFeeLimit',
|
|
199
199
|
adminSecret: this.requireAdminSecret(),
|
|
200
200
|
apiKey,
|
|
201
201
|
limit,
|
|
@@ -215,7 +215,7 @@ class ChannelsClient {
|
|
|
215
215
|
async deleteFeeLimit(apiKey) {
|
|
216
216
|
return this.call({
|
|
217
217
|
management: {
|
|
218
|
-
action:
|
|
218
|
+
action: 'deleteFeeLimit',
|
|
219
219
|
adminSecret: this.requireAdminSecret(),
|
|
220
220
|
apiKey,
|
|
221
221
|
},
|
|
@@ -229,7 +229,7 @@ class ChannelsClient {
|
|
|
229
229
|
*/
|
|
230
230
|
requireAdminSecret() {
|
|
231
231
|
if (!this.adminSecret) {
|
|
232
|
-
throw new Error(
|
|
232
|
+
throw new Error('adminSecret required for management operations. Provide it in client config.');
|
|
233
233
|
}
|
|
234
234
|
return this.adminSecret;
|
|
235
235
|
}
|
|
@@ -262,12 +262,12 @@ class ChannelsClient {
|
|
|
262
262
|
* @throws {PluginUnexpectedError} For invalid/malformed responses
|
|
263
263
|
*/
|
|
264
264
|
validateResponse(responseBody) {
|
|
265
|
-
if (!responseBody || typeof responseBody !==
|
|
266
|
-
throw new errors_1.PluginUnexpectedError(
|
|
265
|
+
if (!responseBody || typeof responseBody !== 'object') {
|
|
266
|
+
throw new errors_1.PluginUnexpectedError('Empty or invalid response from plugin');
|
|
267
267
|
}
|
|
268
268
|
const response = responseBody;
|
|
269
269
|
if (response.success === undefined) {
|
|
270
|
-
throw new errors_1.PluginUnexpectedError(
|
|
270
|
+
throw new errors_1.PluginUnexpectedError('Malformed response: missing success field');
|
|
271
271
|
}
|
|
272
272
|
return response;
|
|
273
273
|
}
|
|
@@ -278,7 +278,8 @@ class ChannelsClient {
|
|
|
278
278
|
* @param metadata Optional metadata (logs and traces)
|
|
279
279
|
* @returns Data with metadata merged if present
|
|
280
280
|
*/
|
|
281
|
-
mergeMetadata(data, metadata
|
|
281
|
+
mergeMetadata(data, metadata // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
282
|
+
) {
|
|
282
283
|
if (!metadata || (!metadata.logs && !metadata.traces)) {
|
|
283
284
|
return data;
|
|
284
285
|
}
|
|
@@ -307,10 +308,8 @@ class ChannelsClient {
|
|
|
307
308
|
const response = this.validateResponse(responseBody);
|
|
308
309
|
// Handle execution errors
|
|
309
310
|
if (!response.success) {
|
|
310
|
-
const errorDetails = response.metadata
|
|
311
|
-
|
|
312
|
-
: response.data;
|
|
313
|
-
throw new errors_1.PluginExecutionError(response.error || "Plugin execution failed", errorDetails);
|
|
311
|
+
const errorDetails = response.metadata ? { ...response.data, metadata: response.metadata } : response.data;
|
|
312
|
+
throw new errors_1.PluginExecutionError(response.error || 'Plugin execution failed', errorDetails);
|
|
314
313
|
}
|
|
315
314
|
// Return data with metadata if present
|
|
316
315
|
return this.mergeMetadata(response.data, response.metadata);
|
|
@@ -327,7 +326,7 @@ class ChannelsClient {
|
|
|
327
326
|
const response = await this.pluginsApi.callPlugin(this.pluginId, payload);
|
|
328
327
|
return response.data;
|
|
329
328
|
}
|
|
330
|
-
const response = await this.axiosClient.post(
|
|
329
|
+
const response = await this.axiosClient.post('/', payload);
|
|
331
330
|
return response.data;
|
|
332
331
|
}
|
|
333
332
|
}
|
package/dist/client/errors.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Base class for all plugin-related errors
|
|
3
3
|
*/
|
|
4
4
|
export declare abstract class PluginClientError extends Error {
|
|
5
|
-
abstract readonly category:
|
|
5
|
+
abstract readonly category: 'transport' | 'execution' | 'client';
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* HTTP/Network transport failures
|
package/dist/client/errors.js
CHANGED
|
@@ -16,12 +16,13 @@ exports.PluginClientError = PluginClientError;
|
|
|
16
16
|
* - Invalid responses (malformed JSON)
|
|
17
17
|
*/
|
|
18
18
|
class PluginTransportError extends PluginClientError {
|
|
19
|
-
constructor(message, statusCode, errorDetails
|
|
19
|
+
constructor(message, statusCode, errorDetails // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
20
|
+
) {
|
|
20
21
|
super(message);
|
|
21
22
|
this.statusCode = statusCode;
|
|
22
23
|
this.errorDetails = errorDetails;
|
|
23
|
-
this.category =
|
|
24
|
-
this.name =
|
|
24
|
+
this.category = 'transport';
|
|
25
|
+
this.name = 'PluginTransportError';
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
exports.PluginTransportError = PluginTransportError;
|
|
@@ -34,11 +35,12 @@ exports.PluginTransportError = PluginTransportError;
|
|
|
34
35
|
* - On-chain failures (transaction reverted)
|
|
35
36
|
*/
|
|
36
37
|
class PluginExecutionError extends PluginClientError {
|
|
37
|
-
constructor(message, errorDetails
|
|
38
|
+
constructor(message, errorDetails // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
39
|
+
) {
|
|
38
40
|
super(message);
|
|
39
41
|
this.errorDetails = errorDetails;
|
|
40
|
-
this.category =
|
|
41
|
-
this.name =
|
|
42
|
+
this.category = 'execution';
|
|
43
|
+
this.name = 'PluginExecutionError';
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
exports.PluginExecutionError = PluginExecutionError;
|
|
@@ -51,11 +53,12 @@ exports.PluginExecutionError = PluginExecutionError;
|
|
|
51
53
|
* - Unexpected response structure
|
|
52
54
|
*/
|
|
53
55
|
class PluginUnexpectedError extends PluginClientError {
|
|
54
|
-
constructor(message, errorDetails
|
|
56
|
+
constructor(message, errorDetails // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
57
|
+
) {
|
|
55
58
|
super(message);
|
|
56
59
|
this.errorDetails = errorDetails;
|
|
57
|
-
this.category =
|
|
58
|
-
this.name =
|
|
60
|
+
this.category = 'client';
|
|
61
|
+
this.name = 'PluginUnexpectedError';
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
exports.PluginUnexpectedError = PluginUnexpectedError;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Unified client for interacting with the channels plugin
|
|
5
5
|
* in both direct HTTP mode and OpenZeppelin Relayer mode.
|
|
6
6
|
*/
|
|
7
|
-
export { ChannelsClient } from
|
|
8
|
-
export { ChannelsClientConfig, DirectHttpConfig, RelayerConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse, } from
|
|
9
|
-
export { PluginClientError, PluginTransportError, PluginExecutionError, PluginUnexpectedError
|
|
7
|
+
export { ChannelsClient } from './channels-client';
|
|
8
|
+
export { ChannelsClientConfig, DirectHttpConfig, RelayerConfig, ChannelsXdrRequest, ChannelsFuncAuthRequest, ChannelsTransactionResponse, ListChannelAccountsResponse, SetChannelAccountsResponse, GetFeeUsageResponse, GetFeeLimitResponse, SetFeeLimitResponse, DeleteFeeLimitResponse, } from './types';
|
|
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,
|
|
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"}
|
package/dist/client/types.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Main entry point for the @openzeppelin/relayer-plugin-channels package
|
|
5
5
|
* Re-exports both the client (for external use) and the plugin handler
|
|
6
6
|
*/
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
7
|
+
export * from './client';
|
|
8
|
+
export * from './plugin';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/plugin/build.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/plugin/build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAe,MAAM,sBAAsB,CAAC;AAIhE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/plugin/build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAe,MAAM,sBAAsB,CAAC;AAIhE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CA2FrE"}
|
package/dist/plugin/build.js
CHANGED
|
@@ -16,30 +16,28 @@ const constants_1 = require("./constants");
|
|
|
16
16
|
* - Copy operations ensuring their source equals the fund address
|
|
17
17
|
*/
|
|
18
18
|
function rebuildWithChannel(params) {
|
|
19
|
-
const { inputXdr, channelAddress, channelSequence, fundAddress, networkPassphrase
|
|
19
|
+
const { inputXdr, channelAddress, channelSequence, fundAddress, networkPassphrase } = params;
|
|
20
20
|
// Parse input transaction
|
|
21
21
|
let inputTx;
|
|
22
22
|
try {
|
|
23
|
-
const envelope = stellar_sdk_1.xdr.TransactionEnvelope.fromXDR(inputXdr,
|
|
23
|
+
const envelope = stellar_sdk_1.xdr.TransactionEnvelope.fromXDR(inputXdr, 'base64');
|
|
24
24
|
// Ensure it's a regular transaction envelope (not fee bump)
|
|
25
25
|
if (envelope.switch() !== stellar_sdk_1.xdr.EnvelopeType.envelopeTypeTx()) {
|
|
26
|
-
throw (0, relayer_sdk_1.pluginError)(
|
|
27
|
-
code:
|
|
26
|
+
throw (0, relayer_sdk_1.pluginError)('Input must be a regular transaction envelope (not fee bump)', {
|
|
27
|
+
code: 'INVALID_ENVELOPE_TYPE',
|
|
28
28
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
inputTx = new stellar_sdk_1.Transaction(envelope, networkPassphrase);
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
|
-
if (error.code ===
|
|
34
|
+
if (error.code === 'INVALID_ENVELOPE_TYPE') {
|
|
35
35
|
throw error;
|
|
36
36
|
}
|
|
37
|
-
throw (0, relayer_sdk_1.pluginError)(
|
|
38
|
-
code:
|
|
37
|
+
throw (0, relayer_sdk_1.pluginError)('Failed to parse input transaction XDR', {
|
|
38
|
+
code: 'INVALID_XDR',
|
|
39
39
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
40
|
-
details: {
|
|
41
|
-
message: error instanceof Error ? error.message : String(error),
|
|
42
|
-
},
|
|
40
|
+
details: { message: error instanceof Error ? error.message : String(error) },
|
|
43
41
|
});
|
|
44
42
|
}
|
|
45
43
|
// Validate timeBounds
|
|
@@ -50,7 +48,7 @@ function rebuildWithChannel(params) {
|
|
|
50
48
|
const maxAllowedTime = now + constants_1.TIME.MAX_TIME_BOUND_OFFSET_SECONDS;
|
|
51
49
|
if (maxTime > maxAllowedTime) {
|
|
52
50
|
throw (0, relayer_sdk_1.pluginError)(`Transaction maxTime is too far in the future. Max allowed: ${constants_1.TIME.MAX_TIME_BOUND_OFFSET_SECONDS} seconds from now`, {
|
|
53
|
-
code:
|
|
51
|
+
code: 'INVALID_TIME_BOUNDS',
|
|
54
52
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
55
53
|
details: { maxTime, maxAllowedTime },
|
|
56
54
|
});
|
|
@@ -62,13 +60,9 @@ function rebuildWithChannel(params) {
|
|
|
62
60
|
const op = inputTx.operations[i];
|
|
63
61
|
if (op.source && op.source !== fundAddress) {
|
|
64
62
|
throw (0, relayer_sdk_1.pluginError)(`Operation ${i} has source ${op.source} but must be ${fundAddress} or omitted`, {
|
|
65
|
-
code:
|
|
63
|
+
code: 'INVALID_OPERATION_SOURCE',
|
|
66
64
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
67
|
-
details: {
|
|
68
|
-
operationIndex: i,
|
|
69
|
-
operationSource: op.source,
|
|
70
|
-
expectedSource: fundAddress,
|
|
71
|
-
},
|
|
65
|
+
details: { operationIndex: i, operationSource: op.source, expectedSource: fundAddress },
|
|
72
66
|
});
|
|
73
67
|
}
|
|
74
68
|
}
|
package/dist/plugin/config.d.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export interface ChannelAccountsConfig {
|
|
7
7
|
fundRelayerId: string;
|
|
8
|
-
network:
|
|
8
|
+
network: 'testnet' | 'mainnet';
|
|
9
9
|
lockTtlSeconds: number;
|
|
10
10
|
adminSecret?: string;
|
|
11
11
|
feeLimit?: number;
|
|
12
12
|
feeResetPeriodMs?: number;
|
|
13
13
|
apiKeyHeader: string;
|
|
14
|
+
limitedContracts: Set<string>;
|
|
15
|
+
contractCapacityRatio: number;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Load configuration from environment variables
|
|
@@ -19,5 +21,5 @@ export declare function loadConfig(): ChannelAccountsConfig;
|
|
|
19
21
|
/**
|
|
20
22
|
* Get the network passphrase based on the configuration
|
|
21
23
|
*/
|
|
22
|
-
export declare function getNetworkPassphrase(network:
|
|
24
|
+
export declare function getNetworkPassphrase(network: 'testnet' | 'mainnet'): string;
|
|
23
25
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -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;
|
|
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"}
|