@openzeppelin/relayer-plugin-channels 0.9.0 → 0.11.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 +60 -0
- package/dist/client/channels-client.d.ts +15 -1
- package/dist/client/channels-client.d.ts.map +1 -1
- package/dist/client/channels-client.js +21 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/types.d.ts +27 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/plugin/config.d.ts +3 -0
- package/dist/plugin/config.d.ts.map +1 -1
- package/dist/plugin/config.js +22 -0
- package/dist/plugin/constants.d.ts +2 -0
- package/dist/plugin/constants.d.ts.map +1 -1
- package/dist/plugin/constants.js +2 -0
- package/dist/plugin/fee.d.ts +8 -5
- package/dist/plugin/fee.d.ts.map +1 -1
- package/dist/plugin/fee.js +8 -10
- package/dist/plugin/handler.d.ts.map +1 -1
- package/dist/plugin/handler.js +65 -23
- package/dist/plugin/management.d.ts.map +1 -1
- package/dist/plugin/management.js +48 -0
- package/dist/plugin/pool.d.ts +1 -0
- package/dist/plugin/pool.d.ts.map +1 -1
- package/dist/plugin/pool.js +12 -4
- package/dist/plugin/sequence.d.ts +30 -0
- package/dist/plugin/sequence.d.ts.map +1 -0
- package/dist/plugin/sequence.js +212 -0
- package/dist/plugin/simulation.d.ts.map +1 -1
- package/dist/plugin/simulation.js +29 -4
- package/dist/plugin/submit.d.ts +13 -1
- package/dist/plugin/submit.d.ts.map +1 -1
- package/dist/plugin/submit.js +84 -3
- package/package.json +1 -1
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,
|
|
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
|
*
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/client/types.d.ts
CHANGED
|
@@ -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":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin/config.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export interface ChannelAccountsConfig {
|
|
|
13
13
|
apiKeyHeader: string;
|
|
14
14
|
limitedContracts: Set<string>;
|
|
15
15
|
contractCapacityRatio: number;
|
|
16
|
+
inclusionFeeDefault: number;
|
|
17
|
+
inclusionFeeLimited: number;
|
|
18
|
+
sequenceNumberCacheMaxAgeMs: number;
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
18
21
|
* Load configuration from environment variables
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/plugin/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
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;IAC5B,2BAA2B,EAAE,MAAM,CAAC;CACrC;AAmGD;;GAEG;AACH,wBAAgB,UAAU,IAAI,qBAAqB,CAuBlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAE3E"}
|
package/dist/plugin/config.js
CHANGED
|
@@ -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,22 @@ 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
|
+
}
|
|
94
|
+
function parseSequenceNumberCacheMaxAge() {
|
|
95
|
+
const raw = process.env.SEQUENCE_NUMBER_CACHE_MAX_AGE_MS;
|
|
96
|
+
if (!raw)
|
|
97
|
+
return constants_1.CONFIG.DEFAULT_SEQUENCE_NUMBER_CACHE_MAX_AGE_MS;
|
|
98
|
+
const n = Number(raw);
|
|
99
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : constants_1.CONFIG.DEFAULT_SEQUENCE_NUMBER_CACHE_MAX_AGE_MS;
|
|
100
|
+
}
|
|
82
101
|
function parseContractCapacityRatio() {
|
|
83
102
|
const raw = process.env.CONTRACT_CAPACITY_RATIO;
|
|
84
103
|
if (!raw)
|
|
@@ -110,6 +129,9 @@ function loadConfig() {
|
|
|
110
129
|
apiKeyHeader: parseApiKeyHeader(),
|
|
111
130
|
limitedContracts: parseLimitedContracts(),
|
|
112
131
|
contractCapacityRatio: parseContractCapacityRatio(),
|
|
132
|
+
inclusionFeeDefault: parseInclusionFee('INCLUSION_FEE_DEFAULT', DEFAULT_INCLUSION_FEE_DEFAULT),
|
|
133
|
+
inclusionFeeLimited: parseInclusionFee('INCLUSION_FEE_LIMITED', DEFAULT_INCLUSION_FEE_LIMITED),
|
|
134
|
+
sequenceNumberCacheMaxAgeMs: parseSequenceNumberCacheMaxAge(),
|
|
113
135
|
};
|
|
114
136
|
}
|
|
115
137
|
/**
|
|
@@ -19,6 +19,7 @@ export declare const CONFIG: {
|
|
|
19
19
|
readonly MIN_LOCK_TTL_SECONDS: 3;
|
|
20
20
|
readonly MAX_LOCK_TTL_SECONDS: 30;
|
|
21
21
|
readonly DEFAULT_CONTRACT_CAPACITY_RATIO: 0.8;
|
|
22
|
+
readonly DEFAULT_SEQUENCE_NUMBER_CACHE_MAX_AGE_MS: 120000;
|
|
22
23
|
};
|
|
23
24
|
export declare const POOL: {
|
|
24
25
|
readonly MUTEX_TTL_SECONDS: 1;
|
|
@@ -34,6 +35,7 @@ export declare const SIMULATION: {
|
|
|
34
35
|
readonly MIN_TIME_BOUND: 0;
|
|
35
36
|
readonly MAX_TIME_BOUND_OFFSET_SECONDS: 120;
|
|
36
37
|
readonly MAX_FUTURE_TIME_BOUND_SECONDS: 120;
|
|
38
|
+
readonly SIMULATION_AUTH_MODE: "enforce";
|
|
37
39
|
};
|
|
38
40
|
export declare const POLLING: {
|
|
39
41
|
readonly INTERVAL_MS: 1000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/plugin/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAGX,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/plugin/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAGX,eAAO,MAAM,MAAM;;;;;;CAMT,CAAC;AAGX,eAAO,MAAM,IAAI;;;;;CAOP,CAAC;AAGX,eAAO,MAAM,IAAI;;CAEP,CAAC;AAGX,eAAO,MAAM,UAAU;;;;;;CAMb,CAAC;AAGX,eAAO,MAAM,OAAO;;;CAGV,CAAC;AAEX,eAAO,MAAM,GAAG;;CAGN,CAAC"}
|
package/dist/plugin/constants.js
CHANGED
|
@@ -24,6 +24,7 @@ exports.CONFIG = {
|
|
|
24
24
|
MIN_LOCK_TTL_SECONDS: 3,
|
|
25
25
|
MAX_LOCK_TTL_SECONDS: 30,
|
|
26
26
|
DEFAULT_CONTRACT_CAPACITY_RATIO: 0.8,
|
|
27
|
+
DEFAULT_SEQUENCE_NUMBER_CACHE_MAX_AGE_MS: 120000,
|
|
27
28
|
};
|
|
28
29
|
// Pool Constants
|
|
29
30
|
exports.POOL = {
|
|
@@ -44,6 +45,7 @@ exports.SIMULATION = {
|
|
|
44
45
|
MIN_TIME_BOUND: 0,
|
|
45
46
|
MAX_TIME_BOUND_OFFSET_SECONDS: 120,
|
|
46
47
|
MAX_FUTURE_TIME_BOUND_SECONDS: 120,
|
|
48
|
+
SIMULATION_AUTH_MODE: 'enforce',
|
|
47
49
|
};
|
|
48
50
|
// Polling for transactionWait
|
|
49
51
|
exports.POLLING = {
|
package/dist/plugin/fee.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
11
|
-
|
|
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
|
|
23
|
+
export declare function calculateMaxFee(transaction: Transaction, limitedContracts: Set<string>, fees: InclusionFees): number;
|
|
21
24
|
//# sourceMappingURL=fee.d.ts.map
|
package/dist/plugin/fee.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
package/dist/plugin/fee.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
51
|
+
return fees.inclusionFeeLimited;
|
|
54
52
|
}
|
|
55
|
-
return
|
|
53
|
+
return fees.inclusionFeeDefault;
|
|
56
54
|
}
|
|
57
|
-
function calculateMaxFee(transaction, limitedContracts
|
|
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);
|
|
@@ -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;
|
|
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;AA0BxD;;;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;AAqPD;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
|
package/dist/plugin/handler.js
CHANGED
|
@@ -20,6 +20,7 @@ const simulation_1 = require("./simulation");
|
|
|
20
20
|
const fee_1 = require("./fee");
|
|
21
21
|
const tx_1 = require("./tx");
|
|
22
22
|
const fee_tracking_1 = require("./fee-tracking");
|
|
23
|
+
const sequence_1 = require("./sequence");
|
|
23
24
|
function getApiKey(headers, headerName) {
|
|
24
25
|
const values = headers[headerName];
|
|
25
26
|
return values?.[0]?.trim() || undefined;
|
|
@@ -44,8 +45,8 @@ function extractFuncAuthFromUnsignedXdr(tx) {
|
|
|
44
45
|
auth: invokeHostFn.auth(),
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
|
-
async function handleXdrSubmit(xdrStr,
|
|
48
|
-
const tx = new stellar_sdk_1.Transaction(xdrStr, networkPassphrase);
|
|
48
|
+
async function handleXdrSubmit(xdrStr, ctx) {
|
|
49
|
+
const tx = new stellar_sdk_1.Transaction(xdrStr, ctx.networkPassphrase);
|
|
49
50
|
// Unsigned XDR: extract func+auth and route through channel path
|
|
50
51
|
if (tx.signatures.length === 0) {
|
|
51
52
|
const extracted = extractFuncAuthFromUnsignedXdr(tx);
|
|
@@ -62,17 +63,22 @@ async function handleXdrSubmit(xdrStr, fundRelayer, fundAddress, network, networ
|
|
|
62
63
|
console.log(`[channels] Unsigned XDR detected, extracting func+auth and routing through channel path`);
|
|
63
64
|
// Update acquireOptions with contractId from extracted func
|
|
64
65
|
const contractId = (0, fee_1.getContractIdFromFunc)(extracted.func);
|
|
65
|
-
const updatedOptions = { ...acquireOptions, contractId };
|
|
66
|
-
return handleFuncAuthSubmit(extracted.func, extracted.auth,
|
|
66
|
+
const updatedOptions = { ...ctx.acquireOptions, contractId };
|
|
67
|
+
return handleFuncAuthSubmit(extracted.func, extracted.auth, { ...ctx, acquireOptions: updatedOptions });
|
|
67
68
|
}
|
|
68
69
|
const validated = (0, tx_1.validateExistingTransactionForSubmitOnly)(tx);
|
|
69
|
-
const maxFee = (0, fee_1.calculateMaxFee)(validated, acquireOptions.limitedContracts);
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const maxFee = (0, fee_1.calculateMaxFee)(validated, ctx.acquireOptions.limitedContracts, ctx.fees);
|
|
71
|
+
const contractId = (0, fee_1.getContractIdFromTransaction)(validated);
|
|
72
|
+
await ctx.tracker?.checkBudget(maxFee);
|
|
73
|
+
const submitContext = {
|
|
74
|
+
contractId,
|
|
75
|
+
isLimited: contractId ? ctx.acquireOptions.limitedContracts.has(contractId) : false,
|
|
76
|
+
};
|
|
77
|
+
return (0, submit_1.submitWithFeeBumpAndWait)(ctx.fundRelayer, validated.toXDR(), ctx.network, maxFee, ctx.api, ctx.tracker, submitContext);
|
|
72
78
|
}
|
|
73
|
-
async function handleFuncAuthSubmit(func, auth,
|
|
79
|
+
async function handleFuncAuthSubmit(func, auth, ctx) {
|
|
74
80
|
// Simulate once — used for both read-only detection and transaction assembly
|
|
75
|
-
const simulation = await (0, simulation_1.simulateTransaction)(func, auth, fundAddress, fundRelayer, networkPassphrase);
|
|
81
|
+
const simulation = await (0, simulation_1.simulateTransaction)(func, auth, ctx.fundAddress, ctx.fundRelayer, ctx.networkPassphrase);
|
|
76
82
|
if (simulation.isReadOnly) {
|
|
77
83
|
console.log(`[channels] Read-only call detected, returning simulation result`);
|
|
78
84
|
return {
|
|
@@ -85,8 +91,8 @@ async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddr
|
|
|
85
91
|
}
|
|
86
92
|
let poolLock;
|
|
87
93
|
try {
|
|
88
|
-
poolLock = await pool.acquire(acquireOptions);
|
|
89
|
-
const channelRelayer = api.useRelayer(poolLock.relayerId);
|
|
94
|
+
poolLock = await ctx.pool.acquire(ctx.acquireOptions);
|
|
95
|
+
const channelRelayer = ctx.api.useRelayer(poolLock.relayerId);
|
|
90
96
|
const channelInfo = await channelRelayer.getRelayer();
|
|
91
97
|
console.log(`[channels] Acquired channel: ${poolLock.relayerId}`);
|
|
92
98
|
if (!channelInfo || !channelInfo.address) {
|
|
@@ -96,24 +102,42 @@ async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddr
|
|
|
96
102
|
details: { relayerId: poolLock.relayerId },
|
|
97
103
|
});
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
if (channelStatus.network_type !== 'stellar') {
|
|
105
|
+
if (channelInfo.network_type !== 'stellar') {
|
|
101
106
|
throw (0, relayer_sdk_1.pluginError)('Channel relayer network type must be stellar', {
|
|
102
107
|
code: 'UNSUPPORTED_NETWORK',
|
|
103
108
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
104
|
-
details: { network_type:
|
|
109
|
+
details: { network_type: channelInfo.network_type, relayerId: poolLock.relayerId },
|
|
105
110
|
});
|
|
106
111
|
}
|
|
112
|
+
const sequence = await (0, sequence_1.getSequence)(ctx.kv, ctx.network, channelRelayer, channelInfo.address, ctx.sequenceNumberCacheMaxAgeMs);
|
|
107
113
|
// 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
|
|
109
|
-
const signedTx = await (0, submit_1.signWithChannelAndFund)(built, channelRelayer, fundRelayer, channelInfo.address, fundAddress, networkPassphrase);
|
|
110
|
-
const maxFee = (0, fee_1.calculateMaxFee)(signedTx, acquireOptions.limitedContracts);
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
const built = (0, simulation_1.buildWithChannel)(func, auth, { address: channelInfo.address, sequence }, ctx.networkPassphrase, simulation.rawSimResult);
|
|
115
|
+
const signedTx = await (0, submit_1.signWithChannelAndFund)(built, channelRelayer, ctx.fundRelayer, channelInfo.address, ctx.fundAddress, ctx.networkPassphrase);
|
|
116
|
+
const maxFee = (0, fee_1.calculateMaxFee)(signedTx, ctx.acquireOptions.limitedContracts, ctx.fees);
|
|
117
|
+
const contractId = (0, fee_1.getContractIdFromFunc)(func);
|
|
118
|
+
await ctx.tracker?.checkBudget(maxFee);
|
|
119
|
+
const submitContext = {
|
|
120
|
+
contractId,
|
|
121
|
+
isLimited: contractId ? ctx.acquireOptions.limitedContracts.has(contractId) : false,
|
|
122
|
+
};
|
|
123
|
+
try {
|
|
124
|
+
const result = await (0, submit_1.submitWithFeeBumpAndWait)(ctx.fundRelayer, signedTx.toXDR(), ctx.network, maxFee, ctx.api, ctx.tracker, submitContext);
|
|
125
|
+
if (result.status === 'confirmed') {
|
|
126
|
+
await (0, sequence_1.commitSequence)(ctx.kv, ctx.network, channelInfo.address, sequence);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
await (0, sequence_1.clearSequence)(ctx.kv, ctx.network, channelInfo.address);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
await (0, sequence_1.clearSequence)(ctx.kv, ctx.network, channelInfo.address);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
113
137
|
}
|
|
114
138
|
finally {
|
|
115
139
|
if (poolLock) {
|
|
116
|
-
await pool.release(poolLock);
|
|
140
|
+
await ctx.pool.release(poolLock);
|
|
117
141
|
}
|
|
118
142
|
}
|
|
119
143
|
}
|
|
@@ -172,16 +196,34 @@ async function channelAccounts(context) {
|
|
|
172
196
|
limitedContracts: config.limitedContracts,
|
|
173
197
|
capacityRatio: config.contractCapacityRatio,
|
|
174
198
|
};
|
|
175
|
-
|
|
199
|
+
const fees = {
|
|
200
|
+
inclusionFeeDefault: config.inclusionFeeDefault,
|
|
201
|
+
inclusionFeeLimited: config.inclusionFeeLimited,
|
|
202
|
+
};
|
|
203
|
+
// 4. Build pipeline context
|
|
204
|
+
const ctx = {
|
|
205
|
+
api,
|
|
206
|
+
kv,
|
|
207
|
+
pool,
|
|
208
|
+
fundRelayer: fundRelayer,
|
|
209
|
+
fundAddress: fundInfo.address,
|
|
210
|
+
network: config.network,
|
|
211
|
+
networkPassphrase,
|
|
212
|
+
acquireOptions,
|
|
213
|
+
fees,
|
|
214
|
+
tracker,
|
|
215
|
+
sequenceNumberCacheMaxAgeMs: config.sequenceNumberCacheMaxAgeMs,
|
|
216
|
+
};
|
|
217
|
+
// 5. Branch by request type
|
|
176
218
|
if (request.type === 'xdr') {
|
|
177
219
|
console.log(`[channels] Flow: XDR submit-only`);
|
|
178
|
-
return await handleXdrSubmit(request.xdr,
|
|
220
|
+
return await handleXdrSubmit(request.xdr, ctx);
|
|
179
221
|
}
|
|
180
222
|
// Extract contractId for func+auth flow
|
|
181
223
|
const contractId = (0, fee_1.getContractIdFromFunc)(request.func);
|
|
182
224
|
const funcAcquireOptions = { ...acquireOptions, contractId };
|
|
183
225
|
console.log(`[channels] Flow: func+auth with channel account`);
|
|
184
|
-
return await handleFuncAuthSubmit(request.func, request.auth,
|
|
226
|
+
return await handleFuncAuthSubmit(request.func, request.auth, { ...ctx, acquireOptions: funcAcquireOptions });
|
|
185
227
|
}
|
|
186
228
|
/**
|
|
187
229
|
* 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;
|
|
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
|
}
|
package/dist/plugin/pool.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/plugin/pool.js
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
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) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequence.ts
|
|
3
|
+
*
|
|
4
|
+
* KV-based sequence number cache for channel accounts.
|
|
5
|
+
* After a transaction is confirmed on-chain, the RPC `getLedgerEntries` can
|
|
6
|
+
* still return the old (pre-increment) sequence number due to read-after-write
|
|
7
|
+
* lag. Caching the next expected sequence in KV avoids `tx_bad_seq` errors.
|
|
8
|
+
*/
|
|
9
|
+
import type { PluginKVStore, Relayer } from '@openzeppelin/relayer-sdk';
|
|
10
|
+
/**
|
|
11
|
+
* Fetch the current sequence number for an account directly from chain
|
|
12
|
+
* via `getLedgerEntries` RPC. Throws on any RPC or decoding failure.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAccountSequence(relayer: Relayer, address: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Get the current sequence number for a channel account.
|
|
17
|
+
* Reads from KV cache first; falls back to chain via `getAccountSequence`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getSequence(kv: PluginKVStore, network: string, relayer: Relayer, address: string, sequenceNumberCacheMaxAgeMs: number): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Store the next expected sequence number in KV after a transaction
|
|
22
|
+
* has been confirmed on-chain.
|
|
23
|
+
*/
|
|
24
|
+
export declare function commitSequence(kv: PluginKVStore, network: string, address: string, usedSequence: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Clear cached sequence — forces a re-fetch from chain on next request.
|
|
27
|
+
* Used when transaction outcome is uncertain (e.g. timeout).
|
|
28
|
+
*/
|
|
29
|
+
export declare function clearSequence(kv: PluginKVStore, network: string, address: string): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=sequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../../src/plugin/sequence.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAwBxE;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsH3F;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,aAAa,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,2BAA2B,EAAE,MAAM,GAClC,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,aAAa,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtG"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* sequence.ts
|
|
4
|
+
*
|
|
5
|
+
* KV-based sequence number cache for channel accounts.
|
|
6
|
+
* After a transaction is confirmed on-chain, the RPC `getLedgerEntries` can
|
|
7
|
+
* still return the old (pre-increment) sequence number due to read-after-write
|
|
8
|
+
* lag. Caching the next expected sequence in KV avoids `tx_bad_seq` errors.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getAccountSequence = getAccountSequence;
|
|
12
|
+
exports.getSequence = getSequence;
|
|
13
|
+
exports.commitSequence = commitSequence;
|
|
14
|
+
exports.clearSequence = clearSequence;
|
|
15
|
+
const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
|
|
16
|
+
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
17
|
+
const constants_1 = require("./constants");
|
|
18
|
+
function seqKey(network, address) {
|
|
19
|
+
return `${network}:channel:seq:${address}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates that a sequence number string is a non-negative integer.
|
|
23
|
+
* Returns true for strings like "0", "42", "123456789012345".
|
|
24
|
+
*/
|
|
25
|
+
function isValidSequence(seq) {
|
|
26
|
+
return /^\d+$/.test(seq);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Fetch the current sequence number for an account directly from chain
|
|
30
|
+
* via `getLedgerEntries` RPC. Throws on any RPC or decoding failure.
|
|
31
|
+
*/
|
|
32
|
+
async function getAccountSequence(relayer, address) {
|
|
33
|
+
let accountKey;
|
|
34
|
+
try {
|
|
35
|
+
accountKey = stellar_sdk_1.xdr.LedgerKey.account(new stellar_sdk_1.xdr.LedgerKeyAccount({
|
|
36
|
+
accountId: stellar_sdk_1.Keypair.fromPublicKey(address).xdrPublicKey(),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('[channels] Sequence fetch failed', {
|
|
41
|
+
event: 'invalid_channel_account_address',
|
|
42
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
43
|
+
address,
|
|
44
|
+
message: error instanceof Error ? error.message : String(error),
|
|
45
|
+
});
|
|
46
|
+
throw (0, relayer_sdk_1.pluginError)('Invalid channel account address', {
|
|
47
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
48
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
49
|
+
details: { address, message: error instanceof Error ? error.message : String(error) },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
let response;
|
|
53
|
+
try {
|
|
54
|
+
response = await relayer.rpc({
|
|
55
|
+
jsonrpc: '2.0',
|
|
56
|
+
id: Math.floor(Math.random() * 1e8).toString(),
|
|
57
|
+
method: 'getLedgerEntries',
|
|
58
|
+
params: {
|
|
59
|
+
keys: [accountKey.toXDR('base64')],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error('[channels] Sequence fetch failed', {
|
|
65
|
+
event: 'sequence_rpc_request_failed',
|
|
66
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
67
|
+
address,
|
|
68
|
+
message: error instanceof Error ? error.message : String(error),
|
|
69
|
+
});
|
|
70
|
+
throw (0, relayer_sdk_1.pluginError)('Failed to get sequence from channel relayer', {
|
|
71
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
72
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
73
|
+
details: { message: error instanceof Error ? error.message : String(error) },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (response.error) {
|
|
77
|
+
console.error('[channels] Sequence fetch failed', {
|
|
78
|
+
event: 'sequence_rpc_error_response',
|
|
79
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
80
|
+
address,
|
|
81
|
+
message: response.error.message,
|
|
82
|
+
});
|
|
83
|
+
throw (0, relayer_sdk_1.pluginError)('Failed to get sequence from channel relayer', {
|
|
84
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
85
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
86
|
+
details: { message: response.error.message },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const result = response.result;
|
|
90
|
+
const entries = result?.entries;
|
|
91
|
+
if (!Array.isArray(entries)) {
|
|
92
|
+
console.error('[channels] Sequence fetch failed', {
|
|
93
|
+
event: 'sequence_rpc_invalid_result_shape',
|
|
94
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
95
|
+
address,
|
|
96
|
+
});
|
|
97
|
+
throw (0, relayer_sdk_1.pluginError)('Invalid RPC response for account sequence', {
|
|
98
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
99
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
100
|
+
details: { address },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (!entries || entries.length === 0) {
|
|
104
|
+
console.warn('[channels] Sequence fetch returned no account entries', {
|
|
105
|
+
event: 'sequence_account_not_found',
|
|
106
|
+
code: 'ACCOUNT_NOT_FOUND',
|
|
107
|
+
address,
|
|
108
|
+
});
|
|
109
|
+
throw (0, relayer_sdk_1.pluginError)('Channel account not found on ledger', {
|
|
110
|
+
code: 'ACCOUNT_NOT_FOUND',
|
|
111
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
112
|
+
details: { address },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const firstEntryXdr = entries[0]?.xdr;
|
|
116
|
+
if (typeof firstEntryXdr !== 'string') {
|
|
117
|
+
console.error('[channels] Sequence fetch failed', {
|
|
118
|
+
event: 'sequence_rpc_invalid_entry_xdr',
|
|
119
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
120
|
+
address,
|
|
121
|
+
});
|
|
122
|
+
throw (0, relayer_sdk_1.pluginError)('Invalid RPC response for account sequence', {
|
|
123
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
124
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
125
|
+
details: { address },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const accountEntry = stellar_sdk_1.xdr.LedgerEntryData.fromXDR(firstEntryXdr, 'base64');
|
|
130
|
+
return accountEntry.account().seqNum().toString();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error('[channels] Sequence fetch failed', {
|
|
134
|
+
event: 'sequence_xdr_decode_failed',
|
|
135
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
136
|
+
address,
|
|
137
|
+
message: error instanceof Error ? error.message : String(error),
|
|
138
|
+
});
|
|
139
|
+
throw (0, relayer_sdk_1.pluginError)('Failed to decode account sequence from ledger entry', {
|
|
140
|
+
code: 'FAILED_TO_GET_SEQUENCE',
|
|
141
|
+
status: constants_1.HTTP_STATUS.BAD_GATEWAY,
|
|
142
|
+
details: { address, message: error instanceof Error ? error.message : String(error) },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get the current sequence number for a channel account.
|
|
148
|
+
* Reads from KV cache first; falls back to chain via `getAccountSequence`.
|
|
149
|
+
*/
|
|
150
|
+
async function getSequence(kv, network, relayer, address, sequenceNumberCacheMaxAgeMs) {
|
|
151
|
+
const key = seqKey(network, address);
|
|
152
|
+
try {
|
|
153
|
+
const cached = await kv.get(key);
|
|
154
|
+
if (cached?.sequence) {
|
|
155
|
+
if (!isValidSequence(cached.sequence)) {
|
|
156
|
+
console.warn(`[channels] Sequence cache invalid: address=${address}, value=${cached.sequence}, refetching`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const age = Date.now() - (cached.storedAt ?? 0);
|
|
160
|
+
if (age < sequenceNumberCacheMaxAgeMs) {
|
|
161
|
+
console.debug(`[channels] Sequence cache hit: address=${address}, seq=${cached.sequence}, age=${age}ms`);
|
|
162
|
+
return cached.sequence;
|
|
163
|
+
}
|
|
164
|
+
console.debug(`[channels] Sequence cache stale: address=${address}, age=${age}ms, refetching`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.warn(`[channels] Failed to read sequence from KV, falling back to chain`, {
|
|
170
|
+
address,
|
|
171
|
+
key,
|
|
172
|
+
error: err instanceof Error ? err.message : String(err),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return getAccountSequence(relayer, address);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Store the next expected sequence number in KV after a transaction
|
|
179
|
+
* has been confirmed on-chain.
|
|
180
|
+
*/
|
|
181
|
+
async function commitSequence(kv, network, address, usedSequence) {
|
|
182
|
+
const next = (BigInt(usedSequence) + 1n).toString();
|
|
183
|
+
const key = seqKey(network, address);
|
|
184
|
+
try {
|
|
185
|
+
await kv.set(key, { sequence: next, storedAt: Date.now() });
|
|
186
|
+
console.debug(`[channels] Sequence committed: address=${address}, next=${next}`);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.warn(`[channels] Failed to commit sequence to KV`, {
|
|
190
|
+
address,
|
|
191
|
+
next,
|
|
192
|
+
error: err instanceof Error ? err.message : String(err),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Clear cached sequence — forces a re-fetch from chain on next request.
|
|
198
|
+
* Used when transaction outcome is uncertain (e.g. timeout).
|
|
199
|
+
*/
|
|
200
|
+
async function clearSequence(kv, network, address) {
|
|
201
|
+
const key = seqKey(network, address);
|
|
202
|
+
try {
|
|
203
|
+
await kv.del(key);
|
|
204
|
+
console.debug(`[channels] Sequence cleared: address=${address}`);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
console.warn(`[channels] Failed to clear sequence from KV`, {
|
|
208
|
+
address,
|
|
209
|
+
error: err instanceof Error ? err.message : String(err),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/plugin/simulation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,GAAG,EAAE,WAAW,EAAsB,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAgD,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGlG,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,CAAC;CACtD;
|
|
1
|
+
{"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/plugin/simulation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,GAAG,EAAE,WAAW,EAAsB,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAgD,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGlG,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,CAAC;CACtD;AAOD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAiF3B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,OAAO,EAAE,cAAc,EACvB,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,GAChD,WAAW,CAsCb;AACD,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAe1D"}
|
|
@@ -41,7 +41,8 @@ async function simulateTransaction(func, auth, sourceAddress, relayer, networkPa
|
|
|
41
41
|
jsonrpc: '2.0',
|
|
42
42
|
id: Math.floor(Math.random() * 1e8).toString(),
|
|
43
43
|
method: 'simulateTransaction',
|
|
44
|
-
|
|
44
|
+
// Enforce mode validates auth entry signatures during simulation.
|
|
45
|
+
params: { transaction: transaction.toXDR(), authMode: constants_1.SIMULATION.SIMULATION_AUTH_MODE },
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
catch (err) {
|
|
@@ -65,11 +66,13 @@ async function simulateTransaction(func, auth, sourceAddress, relayer, networkPa
|
|
|
65
66
|
...rpcResponse.result,
|
|
66
67
|
};
|
|
67
68
|
if ('error' in simResult && simResult.error) {
|
|
69
|
+
const parsedError = parseSimulationError(simResult.error);
|
|
70
|
+
const failure = classifySimulationFailure(simResult.error, parsedError);
|
|
68
71
|
console.error(`[channels] Simulation error: ${simResult.error}`);
|
|
69
|
-
throw (0, relayer_sdk_1.pluginError)(
|
|
70
|
-
code:
|
|
72
|
+
throw (0, relayer_sdk_1.pluginError)(failure.message, {
|
|
73
|
+
code: failure.code,
|
|
71
74
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
72
|
-
details: { error:
|
|
75
|
+
details: { error: parsedError, authMode: constants_1.SIMULATION.SIMULATION_AUTH_MODE },
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
// Read-only detection
|
|
@@ -148,3 +151,25 @@ function parseSimulationError(error) {
|
|
|
148
151
|
}
|
|
149
152
|
return firstLine;
|
|
150
153
|
}
|
|
154
|
+
function classifySimulationFailure(rawError, parsedError) {
|
|
155
|
+
const isEnforcedAuthValidation = constants_1.SIMULATION.SIMULATION_AUTH_MODE === 'enforce' &&
|
|
156
|
+
(/\bError\(Auth,/i.test(rawError) ||
|
|
157
|
+
/\brequire_auth\b/i.test(rawError) ||
|
|
158
|
+
/\binvalid\s+signature\b/i.test(rawError) ||
|
|
159
|
+
/\bsignature\s+has\s+expired\b/i.test(rawError) ||
|
|
160
|
+
/\bsignature\s+expired\b/i.test(rawError) ||
|
|
161
|
+
/\bsignature\s+verification\s+failed\b/i.test(rawError) ||
|
|
162
|
+
/\bbad[_\s]?signature\b/i.test(rawError) ||
|
|
163
|
+
/\btx_bad_auth\b/i.test(rawError) ||
|
|
164
|
+
/\bbad[_\s]?auth\b/i.test(rawError));
|
|
165
|
+
if (isEnforcedAuthValidation) {
|
|
166
|
+
return {
|
|
167
|
+
code: 'SIMULATION_SIGNED_AUTH_VALIDATION_FAILED',
|
|
168
|
+
message: `Signed auth entry validation failed in enforce simulation: ${parsedError}`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
code: 'SIMULATION_FAILED',
|
|
173
|
+
message: 'Simulation failed',
|
|
174
|
+
};
|
|
175
|
+
}
|
package/dist/plugin/submit.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ 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
|
+
}
|
|
14
|
+
interface DecodedTransactionResult {
|
|
15
|
+
feeCharged: number;
|
|
16
|
+
resultCode: string;
|
|
17
|
+
}
|
|
10
18
|
/**
|
|
11
19
|
* Sign transaction with both channel and fund relayers
|
|
12
20
|
* - First sign with channel account
|
|
@@ -17,7 +25,11 @@ export declare function signWithChannelAndFund(transaction: Transaction, channel
|
|
|
17
25
|
/**
|
|
18
26
|
* Submit transaction with fee bump and wait for confirmation
|
|
19
27
|
*/
|
|
20
|
-
export declare function submitWithFeeBumpAndWait(fundRelayer: Relayer, signedXdr: string, network: 'testnet' | 'mainnet', maxFee: number, api: PluginAPI, tracker?: FeeTracker): Promise<ChannelAccountsResponse>;
|
|
28
|
+
export declare function submitWithFeeBumpAndWait(fundRelayer: Relayer, signedXdr: string, network: 'testnet' | 'mainnet', maxFee: number, api: PluginAPI, tracker?: FeeTracker, context?: SubmitContext): Promise<ChannelAccountsResponse>;
|
|
29
|
+
/** Try to decode a transaction result XDR from the reason string */
|
|
30
|
+
export declare function decodeTransactionResult(reason: string): DecodedTransactionResult | null;
|
|
31
|
+
export declare function buildStellarLabTransactionUrl(network: 'testnet' | 'mainnet', txHash: string): string;
|
|
21
32
|
/** Strip provider wrapper text, extract last segment (e.g., "TxInsufficientBalance") */
|
|
22
33
|
export declare function sanitizeReason(reason: string): string;
|
|
34
|
+
export {};
|
|
23
35
|
//# 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,
|
|
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,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;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,CAmFlC;AASD,oEAAoE;AACpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAkCvF;AAeD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAepG;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOrD"}
|
package/dist/plugin/submit.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.signWithChannelAndFund = signWithChannelAndFund;
|
|
9
9
|
exports.submitWithFeeBumpAndWait = submitWithFeeBumpAndWait;
|
|
10
|
+
exports.decodeTransactionResult = decodeTransactionResult;
|
|
11
|
+
exports.buildStellarLabTransactionUrl = buildStellarLabTransactionUrl;
|
|
10
12
|
exports.sanitizeReason = sanitizeReason;
|
|
11
13
|
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
12
14
|
const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
|
|
@@ -40,7 +42,7 @@ async function signWithChannelAndFund(transaction, channelRelayer, _fundRelayer,
|
|
|
40
42
|
/**
|
|
41
43
|
* Submit transaction with fee bump and wait for confirmation
|
|
42
44
|
*/
|
|
43
|
-
async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee, api, tracker) {
|
|
45
|
+
async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee, api, tracker, context) {
|
|
44
46
|
// Submit with fee bump
|
|
45
47
|
console.debug(`[channels] Sending fee bump tx: network=${network}, maxFee=${maxFee}, xdr_len=${signedXdr.length}`);
|
|
46
48
|
const payload = {
|
|
@@ -64,9 +66,25 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
|
|
|
64
66
|
await tracker.recordUsage(maxFee);
|
|
65
67
|
}
|
|
66
68
|
const rawReason = final.status_reason || 'Transaction failed';
|
|
67
|
-
|
|
69
|
+
const decoded = decodeTransactionResult(rawReason);
|
|
70
|
+
const labUrl = final.hash ? buildStellarLabTransactionUrl(network, final.hash) : null;
|
|
71
|
+
const contractType = context?.isLimited ? 'limited' : 'default';
|
|
72
|
+
const base = `[channels] Transaction failed: contractId=${context?.contractId ?? 'unknown'}, contractType=${contractType}, maxFee=${maxFee}`;
|
|
73
|
+
if (decoded && isTxInsufficientFeeError(decoded.resultCode)) {
|
|
74
|
+
const feeInfo = decoded.feeCharged != null
|
|
75
|
+
? `, requiredFee=${decoded.feeCharged}, shortfall=${decoded.feeCharged - maxFee}`
|
|
76
|
+
: '';
|
|
77
|
+
console.error(`${base}, reason=txInsufficientFee${feeInfo}`);
|
|
78
|
+
}
|
|
79
|
+
else if (decoded) {
|
|
80
|
+
console.error(`${base}, reason=${decoded.resultCode}`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.error(`${base}, reason=${rawReason}`);
|
|
84
|
+
}
|
|
68
85
|
const reason = sanitizeReason(rawReason);
|
|
69
|
-
|
|
86
|
+
const reasonWithLab = labUrl ? `${reason}. Debug in Stellar Lab (click "Load Transaction"): ${labUrl}` : reason;
|
|
87
|
+
throw (0, relayer_sdk_1.pluginError)(reasonWithLab, {
|
|
70
88
|
code: 'ONCHAIN_FAILED',
|
|
71
89
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
72
90
|
details: {
|
|
@@ -74,6 +92,8 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
|
|
|
74
92
|
reason,
|
|
75
93
|
id: final.id,
|
|
76
94
|
hash: final.hash ?? null,
|
|
95
|
+
resultCode: decoded?.resultCode ?? null,
|
|
96
|
+
labUrl: labUrl ? `Debug this failure in Stellar Lab (click "Load Transaction"): ${labUrl}` : null,
|
|
77
97
|
},
|
|
78
98
|
});
|
|
79
99
|
}
|
|
@@ -109,6 +129,67 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
|
|
|
109
129
|
function isSignTransactionResponseStellar(data) {
|
|
110
130
|
return data !== null && typeof data === 'object' && 'signature' in data && 'signedXdr' in data;
|
|
111
131
|
}
|
|
132
|
+
/** Try to decode a transaction result XDR from the reason string */
|
|
133
|
+
function decodeTransactionResult(reason) {
|
|
134
|
+
const fromReasonText = extractResultCodeFromReasonText(reason);
|
|
135
|
+
if (fromReasonText) {
|
|
136
|
+
return {
|
|
137
|
+
feeCharged: 0,
|
|
138
|
+
resultCode: fromReasonText,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const match = reason.match(/([A-Za-z0-9+/=]{20,})$/);
|
|
143
|
+
if (!match)
|
|
144
|
+
return null;
|
|
145
|
+
const result = stellar_sdk_1.xdr.TransactionResult.fromXDR(match[1], 'base64');
|
|
146
|
+
const outerResultCode = String(result.result().switch().name);
|
|
147
|
+
let resultCode = outerResultCode;
|
|
148
|
+
// Unwrap fee bump inner failure to get the actual result code
|
|
149
|
+
if (outerResultCode === 'txFeeBumpInnerFailed') {
|
|
150
|
+
try {
|
|
151
|
+
const innerResult = result.result().innerResultPair().result();
|
|
152
|
+
const innerResultCode = String(innerResult.result().switch().name);
|
|
153
|
+
resultCode = `${outerResultCode}:${innerResultCode}`;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// keep outer result code if unwrap fails
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
feeCharged: Number(result.feeCharged().toBigInt()),
|
|
161
|
+
resultCode,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function extractResultCodeFromReasonText(reason) {
|
|
169
|
+
const outerMatch = reason.match(/\bSpecific XDR reason:\s*([A-Za-z0-9_]+)\b/i);
|
|
170
|
+
if (!outerMatch?.[1])
|
|
171
|
+
return null;
|
|
172
|
+
const innerMatch = reason.match(/\bInner result:\s*([A-Za-z0-9_]+)\b/i);
|
|
173
|
+
return innerMatch?.[1] ? `${outerMatch[1]}:${innerMatch[1]}` : outerMatch[1];
|
|
174
|
+
}
|
|
175
|
+
/** Check if the result code indicates an insufficient fee error (case-insensitive). */
|
|
176
|
+
function isTxInsufficientFeeError(resultCode) {
|
|
177
|
+
return resultCode?.toLowerCase() === 'txinsufficientfee';
|
|
178
|
+
}
|
|
179
|
+
function buildStellarLabTransactionUrl(network, txHash) {
|
|
180
|
+
const isMainnet = network === 'mainnet';
|
|
181
|
+
const networkId = isMainnet ? 'mainnet' : 'testnet';
|
|
182
|
+
const label = isMainnet ? 'Mainnet' : 'Testnet';
|
|
183
|
+
const horizonUrl = isMainnet ? 'https://horizon.stellar.org' : 'https://horizon-testnet.stellar.org';
|
|
184
|
+
const rpcUrl = isMainnet ? 'https://mainnet.sorobanrpc.com' : 'https://soroban-testnet.stellar.org';
|
|
185
|
+
const passphrase = isMainnet ? 'Public Global Stellar Network ; September 2015' : 'Test SDF Network ; September 2015';
|
|
186
|
+
// Stellar Lab expects protocol values encoded as https://// in query params.
|
|
187
|
+
// txHash is intentionally left unencoded because it is a hex string.
|
|
188
|
+
const horizonParam = horizonUrl.replace('https://', 'https:////');
|
|
189
|
+
const rpcParam = rpcUrl.replace('https://', 'https:////');
|
|
190
|
+
const passphraseParam = passphrase.replace(/ /g, '%20').replace(/;/g, '%3B');
|
|
191
|
+
return `https://lab.stellar.org/transaction/dashboard?$=network$id=${networkId}&label=${label}&horizonUrl=${horizonParam}&rpcUrl=${rpcParam}&passphrase=${passphraseParam}&txDashboard$transactionHash=${txHash};;`;
|
|
192
|
+
}
|
|
112
193
|
/** Strip provider wrapper text, extract last segment (e.g., "TxInsufficientBalance") */
|
|
113
194
|
function sanitizeReason(reason) {
|
|
114
195
|
const segments = reason.split(/:\s*/);
|