@openzeppelin/relayer-plugin-channels 0.2.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +504 -0
  3. package/dist/build.d.ts +21 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +100 -0
  6. package/dist/config.d.ts +31 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +89 -0
  9. package/dist/constants.d.ts +46 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/constants.js +56 -0
  12. package/dist/fee.d.ts +11 -0
  13. package/dist/fee.d.ts.map +1 -0
  14. package/dist/fee.js +41 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +115 -0
  18. package/dist/management.d.ts +11 -0
  19. package/dist/management.d.ts.map +1 -0
  20. package/dist/management.js +166 -0
  21. package/dist/pool.d.ts +31 -0
  22. package/dist/pool.d.ts.map +1 -0
  23. package/dist/pool.js +129 -0
  24. package/dist/simulation.d.ts +14 -0
  25. package/dist/simulation.d.ts.map +1 -0
  26. package/dist/simulation.js +52 -0
  27. package/dist/submit.d.ts +20 -0
  28. package/dist/submit.d.ts.map +1 -0
  29. package/dist/submit.js +102 -0
  30. package/dist/test/config.test.d.ts +2 -0
  31. package/dist/test/config.test.d.ts.map +1 -0
  32. package/dist/test/config.test.js +43 -0
  33. package/dist/test/fee.test.d.ts +2 -0
  34. package/dist/test/fee.test.d.ts.map +1 -0
  35. package/dist/test/fee.test.js +34 -0
  36. package/dist/test/helpers/fakeKV.d.ts +17 -0
  37. package/dist/test/helpers/fakeKV.d.ts.map +1 -0
  38. package/dist/test/helpers/fakeKV.js +60 -0
  39. package/dist/test/management.test.d.ts +2 -0
  40. package/dist/test/management.test.d.ts.map +1 -0
  41. package/dist/test/management.test.js +66 -0
  42. package/dist/test/pool.busy.test.d.ts +2 -0
  43. package/dist/test/pool.busy.test.d.ts.map +1 -0
  44. package/dist/test/pool.busy.test.js +23 -0
  45. package/dist/test/pool.test.d.ts +2 -0
  46. package/dist/test/pool.test.d.ts.map +1 -0
  47. package/dist/test/pool.test.js +29 -0
  48. package/dist/test/tx.test.d.ts +2 -0
  49. package/dist/test/tx.test.d.ts.map +1 -0
  50. package/dist/test/tx.test.js +29 -0
  51. package/dist/test/validation.test.d.ts +2 -0
  52. package/dist/test/validation.test.d.ts.map +1 -0
  53. package/dist/test/validation.test.js +31 -0
  54. package/dist/tx.d.ts +8 -0
  55. package/dist/tx.d.ts.map +1 -0
  56. package/dist/tx.js +45 -0
  57. package/dist/types.d.ts +66 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +7 -0
  60. package/dist/validation.d.ts +9 -0
  61. package/dist/validation.d.ts.map +1 -0
  62. package/dist/validation.js +84 -0
  63. package/package.json +63 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OpenZeppelin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,504 @@
1
+ # Channels Plugin
2
+
3
+ A plugin for OpenZeppelin Relayer that enables parallel transaction submission on Stellar using channel accounts with fee bumping. Channel accounts provide unique sequence numbers for parallel transaction submission, preventing sequence number conflicts.
4
+
5
+ ## Quick Start
6
+
7
+ **Want to get started quickly?** Check out the [Channels Plugin Example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example) which includes a pre-configured relayer setup, Docker Compose configuration, and step-by-step instructions. This is the fastest way to get the Channels plugin up and running.
8
+
9
+ For manual installation and configuration details, continue reading below.
10
+
11
+ ## Prerequisites
12
+
13
+ - Node.js >= 18
14
+ - pnpm >= 10
15
+ - OpenZeppelin Relayer
16
+
17
+ ## Installation & Setup
18
+
19
+ The Channels plugin can be added to any OpenZeppelin Relayer in two ways:
20
+
21
+ ### 1. Install from npm (recommended)
22
+
23
+ ```bash
24
+ # From the root of your Relayer repository
25
+ cd plugins
26
+ mkdir channels
27
+ cd channels
28
+ pnpm add @openzeppelin/relayer-plugin-channels
29
+ ```
30
+
31
+ ### 2. Use a local build (for development / debugging)
32
+
33
+ ```bash
34
+ # Clone and build the plugin
35
+ git clone https://github.com/openzeppelin/relayer-plugin-channels.git
36
+ cd relayer-plugin-channels
37
+ pnpm install
38
+ pnpm build
39
+ ```
40
+
41
+ Now reference the local build from your Relayer's `plugins/package.json`:
42
+
43
+ ```jsonc
44
+ {
45
+ "dependencies": {
46
+ "@openzeppelin/relayer-plugin-channels": "file:../../relayer-plugin-channels",
47
+ },
48
+ }
49
+ ```
50
+
51
+ Install dependencies:
52
+
53
+ ```bash
54
+ pnpm install
55
+ ```
56
+
57
+ ---
58
+
59
+ ### Create the plugin wrapper
60
+
61
+ Inside the Relayer create a directory for the plugin and expose its handler:
62
+
63
+ ```bash
64
+ mkdir -p plugins/channels
65
+ ```
66
+
67
+ `plugins/channels/index.ts`
68
+
69
+ ```ts
70
+ export { handler } from "@openzeppelin/relayer-plugin-channels";
71
+ ```
72
+
73
+ ### Configure the Relayer
74
+
75
+ Before setting environment variables, you need to configure your Relayer's `config.json` with the fund account and channel accounts. Create or update your `config/config.json`:
76
+
77
+ ```json
78
+ {
79
+ "relayers": [
80
+ {
81
+ "id": "channels-fund",
82
+ "name": "Channels Fund Account",
83
+ "network": "testnet",
84
+ "paused": false,
85
+ "network_type": "stellar",
86
+ "signer_id": "channels-fund-signer",
87
+ "policies": {
88
+ "concurrent_transactions": true
89
+ }
90
+ },
91
+ {
92
+ "id": "channel-001",
93
+ "name": "Channel Account 001",
94
+ "network": "testnet",
95
+ "paused": false,
96
+ "network_type": "stellar",
97
+ "signer_id": "channel-001-signer"
98
+ },
99
+ {
100
+ "id": "channel-002",
101
+ "name": "Channel Account 002",
102
+ "network": "testnet",
103
+ "paused": false,
104
+ "network_type": "stellar",
105
+ "signer_id": "channel-002-signer"
106
+ }
107
+ ],
108
+ "notifications": [],
109
+ "signers": [
110
+ {
111
+ "id": "channels-fund-signer",
112
+ "type": "local",
113
+ "config": {
114
+ "path": "config/keys/channels-fund.json",
115
+ "passphrase": {
116
+ "type": "env",
117
+ "value": "KEYSTORE_PASSPHRASE_FUND"
118
+ }
119
+ }
120
+ },
121
+ {
122
+ "id": "channel-001-signer",
123
+ "type": "local",
124
+ "config": {
125
+ "path": "config/keys/channel-001.json",
126
+ "passphrase": {
127
+ "type": "env",
128
+ "value": "KEYSTORE_PASSPHRASE_CHANNEL_001"
129
+ }
130
+ }
131
+ },
132
+ {
133
+ "id": "channel-002-signer",
134
+ "type": "local",
135
+ "config": {
136
+ "path": "config/keys/channel-002.json",
137
+ "passphrase": {
138
+ "type": "env",
139
+ "value": "KEYSTORE_PASSPHRASE_CHANNEL_002"
140
+ }
141
+ }
142
+ }
143
+ ],
144
+ "networks": "./config/networks",
145
+ "plugins": [
146
+ {
147
+ "id": "channels",
148
+ "path": "channel/index.ts",
149
+ "timeout": 30,
150
+ "emit_logs": true,
151
+ "emit_traces": true
152
+ }
153
+ ]
154
+ }
155
+ ```
156
+
157
+ **Important Configuration Notes:**
158
+
159
+ - **Fund Account** (`channels-fund`): Must have `"concurrent_transactions": true` in policies to enable parallel transaction processing
160
+ - **Channel Accounts**: Create at least 2 for better throughput (you can add more as `channel-003`, etc.)
161
+ - **Network**: Use `testnet` for testing or `mainnet` for production
162
+ - **Signers**: Each relayer references a signer by `signer_id`, and signers are defined separately with keystore paths
163
+ - **Keystore Files**: You'll need to create keystore files for each account - see [OpenZeppelin Relayer documentation](https://docs.openzeppelin.com/relayer) for details on creating and managing keys
164
+ - **Plugin Registration**: The `path` points to your plugin wrapper file relative to the plugins directory
165
+
166
+ For more details on Relayer configuration, see the [OpenZeppelin Relayer documentation](https://docs.openzeppelin.com/relayer).
167
+
168
+ ### Configure Environment Variables
169
+
170
+ Set the required environment variables for the plugin:
171
+
172
+ ```bash
173
+ # Required environment variables
174
+ export STELLAR_NETWORK="testnet" # or "mainnet"
175
+ export SOROBAN_RPC_URL="https://soroban-testnet.stellar.org"
176
+ export FUND_RELAYER_ID="channels-fund"
177
+ export PLUGIN_ADMIN_SECRET="your-secret-here" # Required for management API
178
+
179
+ # Optional environment variables
180
+ export LOCK_TTL_SECONDS=10 # default: 30, min: 3, max: 30
181
+ export MAX_FEE=1000000 # default: 1,000,000 stroops
182
+ ```
183
+
184
+ Your Relayer should now contain:
185
+
186
+ ```
187
+ relayer/
188
+ └─ plugins/
189
+ └─ channels/
190
+ ├─ package.json # lists the dependency
191
+ └─ index.ts
192
+ ```
193
+
194
+ ### Initialize Channel Accounts
195
+
196
+ Before using the Channels plugin, you must configure channel accounts using the management API:
197
+
198
+ ```bash
199
+ curl -X POST http://localhost:8080/api/v1/plugins/channels/call \
200
+ -H "Authorization: Bearer YOUR_API_KEY" \
201
+ -H "Content-Type: application/json" \
202
+ -d '{
203
+ "params": {
204
+ "management": {
205
+ "action": "setChannelAccounts",
206
+ "adminSecret": "your-secret-here",
207
+ "relayerIds": ["channel-0001", "channel-0002", "channel-0003"]
208
+ }
209
+ }
210
+ }'
211
+ ```
212
+
213
+ The Channels plugin is now ready to serve Soroban transactions 🚀
214
+
215
+ ## Development
216
+
217
+ ### Building from Source
218
+
219
+ ```bash
220
+ # Install dependencies
221
+ pnpm install
222
+
223
+ # Build the plugin
224
+ pnpm build
225
+
226
+ # Run tests
227
+ pnpm test
228
+
229
+ # Lint and format
230
+ pnpm lint
231
+ pnpm format
232
+ ```
233
+
234
+ ## Overview
235
+
236
+ The Channels plugin accepts Soroban operations and handles all the complexity of getting them on-chain:
237
+
238
+ - Automatic fee bumping using a dedicated fund account
239
+ - Parallel transaction execution with a pool of channel accounts
240
+ - Transaction simulation and resource management
241
+ - Error handling and confirmation waiting
242
+
243
+ ## Architecture
244
+
245
+ - **Fund Account**: Holds funds and pays for fee bumps
246
+ - **Channel Accounts**: Provide unique sequence numbers for parallel transaction submission
247
+ - The channel account is the transaction source and signer; the fund account wraps it in a fee bump
248
+
249
+ ## Management API
250
+
251
+ The Channels plugin provides a management API to dynamically configure channel accounts. This API requires authentication via the `PLUGIN_ADMIN_SECRET` environment variable.
252
+
253
+ ### List Channel Accounts
254
+
255
+ Get the current list of configured channel accounts:
256
+
257
+ ```bash
258
+ curl -X POST http://localhost:8080/api/v1/plugins/channels/call \
259
+ -H "Authorization: Bearer YOUR_API_KEY" \
260
+ -H "Content-Type: application/json" \
261
+ -d '{
262
+ "params": {
263
+ "management": {
264
+ "action": "listChannelAccounts",
265
+ "adminSecret": "your-secret-here"
266
+ }
267
+ }
268
+ }'
269
+ ```
270
+
271
+ **Response:**
272
+
273
+ ```json
274
+ {
275
+ "relayerIds": ["channel-0001", "channel-0002", "channel-0003"]
276
+ }
277
+ ```
278
+
279
+ ### Set Channel Accounts
280
+
281
+ Configure the channel accounts that the plugin will use. This replaces the entire list:
282
+
283
+ ```bash
284
+ curl -X POST http://localhost:8080/api/v1/plugins/channels/call \
285
+ -H "Authorization: Bearer YOUR_API_KEY" \
286
+ -H "Content-Type: application/json" \
287
+ -d '{
288
+ "params": {
289
+ "management": {
290
+ "action": "setChannelAccounts",
291
+ "adminSecret": "your-secret-here",
292
+ "relayerIds": ["channel-0001", "channel-0002", "channel-0003"]
293
+ }
294
+ }
295
+ }'
296
+ ```
297
+
298
+ **Response:**
299
+
300
+ ```json
301
+ {
302
+ "ok": true,
303
+ "appliedRelayerIds": ["channel-0001", "channel-0002", "channel-0003"]
304
+ }
305
+ ```
306
+
307
+ **Important Notes:**
308
+
309
+ - You must configure at least one channel account before the plugin can process transactions
310
+ - The management API will prevent removing accounts that are currently locked (in use). On failure it throws a plugin error with status 409, code `LOCKED_CONFLICT`, and `details.locked` listing blocked IDs.
311
+ - All relayer IDs must exist in your OpenZeppelin Relayer configuration
312
+ - The `adminSecret` must match the `PLUGIN_ADMIN_SECRET` environment variable
313
+
314
+ ## API Usage
315
+
316
+ ### Submit with Transaction XDR
317
+
318
+ Submit a complete, signed transaction:
319
+
320
+ ```bash
321
+ curl -X POST http://localhost:8080/api/v1/plugins/channels/call \
322
+ -H "Authorization: Bearer YOUR_API_KEY" \
323
+ -H "Content-Type: application/json" \
324
+ -d '{
325
+ "params": {
326
+ "xdr": "AAAAAgAAAAB..."
327
+ }
328
+ }'
329
+ ```
330
+
331
+ ### Submit with Function and Auth
332
+
333
+ Submit just the Soroban function and auth entries:
334
+
335
+ ```bash
336
+ curl -X POST http://localhost:8080/api/v1/plugins/channels/call \
337
+ -H "Authorization: Bearer YOUR_API_KEY" \
338
+ -H "Content-Type: application/json" \
339
+ -d '{
340
+ "params": {
341
+ "func": "AAAABAAAAAEAAAAGc3ltYm9s...",
342
+ "auth": ["AAAACAAAAAEAAAA..."]
343
+ }
344
+ }'
345
+ ```
346
+
347
+ ### Parameters
348
+
349
+ - `xdr` (string): Complete transaction envelope XDR (signed, not fee-bump)
350
+ - `func` (string): Soroban host function XDR (base64)
351
+ - `auth` (array): Array of Soroban authorization entry XDRs (base64)
352
+
353
+ **Note**: Provide either `xdr` OR `func`+`auth`, not both.
354
+
355
+ ### Response
356
+
357
+ Responses follow the Relayer envelope `{ success, data, error }`.
358
+
359
+ Success example:
360
+
361
+ ```json
362
+ {
363
+ "success": true,
364
+ "data": {
365
+ "transactionId": "tx_123456",
366
+ "status": "confirmed",
367
+ "hash": "1234567890abcdef..."
368
+ },
369
+ "error": null
370
+ }
371
+ ```
372
+
373
+ Plugin error example:
374
+
375
+ ```json
376
+ {
377
+ "success": false,
378
+ "data": {
379
+ "code": "POOL_CAPACITY",
380
+ "details": {}
381
+ },
382
+ "error": "Too many transactions queued. Please try again later"
383
+ }
384
+ ```
385
+
386
+ ## How It Works
387
+
388
+ 1. **Request Validation**: Validates input parameters (xdr OR func+auth)
389
+ 2. **Channel Account Pool**: Acquires an available channel account from the pool
390
+ 3. **Transaction Building**: For func+auth, builds transaction with channel as source
391
+ 4. **Simulation**: Simulates transaction to obtain sorobanData and resource fee
392
+ 5. **Signing**: Channel account signs the transaction
393
+ 6. **Fee Calculation**: Calculates dynamic max_fee based on resource fee
394
+ 7. **Fee Bumping**: Fund account wraps transaction with fee bump
395
+ 8. **Submission**: Sends to Stellar network and waits for confirmation
396
+ 9. **Pool Release**: Returns channel account to the pool
397
+
398
+ ## Validation Rules
399
+
400
+ ### Input Validation
401
+
402
+ - Must provide `xdr` OR `func`+`auth` (not both)
403
+ - XDR must not be a fee-bump envelope
404
+ - All parameters must be valid base64 XDR
405
+
406
+ ### Transaction Validation (XDR mode)
407
+
408
+ - Envelope type must be `envelopeTypeTx` (not fee bump)
409
+ - TimeBounds maxTime must be within 30 seconds from now
410
+
411
+ ## KV Schema
412
+
413
+ ### Membership List
414
+
415
+ - **Key**: `<network>:channel:relayer-ids`
416
+ - **Value**: `{ relayerIds: string[] }`
417
+
418
+ ### Channel Locks
419
+
420
+ - **Key**: `<network>:channel:in-use:<relayerId>`
421
+ - **Value**: `{ token: string, lockedAt: ISOString }`
422
+ - **TTL**: Configured by `LOCK_TTL_SECONDS`.
423
+
424
+ ## Error Codes
425
+
426
+ - `CONFIG_MISSING`: Missing required environment variable
427
+ - `UNSUPPORTED_NETWORK`: Invalid network type
428
+ - `INVALID_PARAMS`: Invalid request parameters
429
+ - `INVALID_XDR`: Failed to parse XDR
430
+ - `INVALID_ENVELOPE_TYPE`: Not a regular transaction envelope
431
+ - `INVALID_TIME_BOUNDS`: TimeBounds too far in the future
432
+ - `NO_CHANNELS_CONFIGURED`: No channel accounts have been configured via management API
433
+ - `POOL_CAPACITY`: All channel accounts in use
434
+ - `RELAYER_UNAVAILABLE`: Relayer not found
435
+ - `SIMULATION_FAILED`: Transaction simulation failed
436
+ - `ONCHAIN_FAILED`: Transaction failed on-chain
437
+ - `WAIT_TIMEOUT`: Transaction wait timeout
438
+ - `MANAGEMENT_DISABLED`: Management API not enabled
439
+ - `UNAUTHORIZED`: Invalid admin secret
440
+ - `LOCKED_CONFLICT`: Cannot remove locked channel accounts
441
+
442
+ ## License
443
+
444
+ MIT
445
+
446
+ ---
447
+
448
+ ## Smoke Test Contract
449
+
450
+ This repo includes a minimal Soroban contract and smoke test script that exercise the Channels plugin with different authorization methods.
451
+
452
+ ### Contract
453
+
454
+ - **Path**: `contracts/smoke-contract`
455
+ - **Functions**:
456
+ - `no_auth_bump(n: u32) -> u32` — No auth required; returns n+1
457
+ - `write_with_address_auth(addr: Address, value: u32)` — Requires address auth; writes value to storage
458
+ - `read_value(addr: Address) -> u32` — Reads stored value for address
459
+
460
+ Build/optimize/deploy with the Stellar CLI:
461
+
462
+ ```bash
463
+ # Using helper script
464
+ bash contracts/smoke-contract/contract.sh build
465
+ bash contracts/smoke-contract/contract.sh optimize
466
+ bash contracts/smoke-contract/contract.sh deploy --network testnet --account test-account
467
+ ```
468
+
469
+ ### Smoke Test Script
470
+
471
+ - **Path**: `scripts/smoke.ts`
472
+ - **Requirements**:
473
+ - Node.js 18+
474
+ - Stellar CLI (`stellar`) configured with a key
475
+ - Channels plugin running at base URL
476
+
477
+ Environment variables:
478
+
479
+ ```bash
480
+ export BASE_URL="http://localhost:8080" # relayer origin
481
+ export API_KEY="<relayer-api-key>" # required
482
+ export NETWORK="testnet" # or "mainnet"
483
+ export RPC_URL="https://soroban-testnet.stellar.org"
484
+ export ACCOUNT_NAME="test-account" # key in `stellar keys`
485
+ export CONTRACT_ID="CDXX..." # deployed smoke-contract
486
+ ```
487
+
488
+ Run (args override env):
489
+
490
+ ```bash
491
+ pnpm ts-node scripts/smoke.ts \
492
+ --api-key YOUR_API_KEY \
493
+ --account-name test-account \
494
+ --contract-id CDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
495
+
496
+ # Show all options in the script header
497
+ ```
498
+
499
+ What it does:
500
+
501
+ - Health-checks the relayer
502
+ - XDR submit-only: signs a small self-payment and submits via fee bump
503
+ - func+auth (no auth): calls `no_auth_bump(42)` using a channel account
504
+ - func+auth (address auth): calls `write_with_address_auth(addr, 777)` with signed auth entries
@@ -0,0 +1,21 @@
1
+ /**
2
+ * build.ts
3
+ *
4
+ * Transaction rebuild logic to use channel account as source.
5
+ */
6
+ import { Transaction } from "@stellar/stellar-sdk";
7
+ export interface RebuildParams {
8
+ inputXdr: string;
9
+ channelAddress: string;
10
+ channelSequence: string;
11
+ fundAddress: string;
12
+ networkPassphrase: string;
13
+ }
14
+ /**
15
+ * Rebuild transaction with channel account as source
16
+ * - Set transaction source to channel account with current sequence
17
+ * - Preserve memo, timeBounds, etc.
18
+ * - Copy operations ensuring their source equals the fund address
19
+ */
20
+ export declare function rebuildWithChannel(params: RebuildParams): Transaction;
21
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/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,CA6GrE"}
package/dist/build.js ADDED
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * build.ts
4
+ *
5
+ * Transaction rebuild logic to use channel account as source.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.rebuildWithChannel = rebuildWithChannel;
9
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
10
+ const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
11
+ const constants_1 = require("./constants");
12
+ /**
13
+ * Rebuild transaction with channel account as source
14
+ * - Set transaction source to channel account with current sequence
15
+ * - Preserve memo, timeBounds, etc.
16
+ * - Copy operations ensuring their source equals the fund address
17
+ */
18
+ function rebuildWithChannel(params) {
19
+ const { inputXdr, channelAddress, channelSequence, fundAddress, networkPassphrase, } = params;
20
+ // Parse input transaction
21
+ let inputTx;
22
+ try {
23
+ const envelope = stellar_sdk_1.xdr.TransactionEnvelope.fromXDR(inputXdr, "base64");
24
+ // Ensure it's a regular transaction envelope (not fee bump)
25
+ if (envelope.switch() !== stellar_sdk_1.xdr.EnvelopeType.envelopeTypeTx()) {
26
+ throw (0, relayer_sdk_1.pluginError)("Input must be a regular transaction envelope (not fee bump)", {
27
+ code: "INVALID_ENVELOPE_TYPE",
28
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
29
+ });
30
+ }
31
+ inputTx = new stellar_sdk_1.Transaction(envelope, networkPassphrase);
32
+ }
33
+ catch (error) {
34
+ if (error.code === "INVALID_ENVELOPE_TYPE") {
35
+ throw error;
36
+ }
37
+ throw (0, relayer_sdk_1.pluginError)("Failed to parse input transaction XDR", {
38
+ code: "INVALID_XDR",
39
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
40
+ details: {
41
+ message: error instanceof Error ? error.message : String(error),
42
+ },
43
+ });
44
+ }
45
+ // Validate timeBounds
46
+ if (inputTx.timeBounds) {
47
+ const now = Math.floor(Date.now() / 1000);
48
+ const maxTime = Number(inputTx.timeBounds.maxTime);
49
+ if (maxTime > 0) {
50
+ const maxAllowedTime = now + constants_1.TIME.MAX_TIME_BOUND_OFFSET_SECONDS;
51
+ if (maxTime > maxAllowedTime) {
52
+ 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: "INVALID_TIME_BOUNDS",
54
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
55
+ details: { maxTime, maxAllowedTime },
56
+ });
57
+ }
58
+ }
59
+ }
60
+ // Validate operations: all operation sources must be fund address or missing
61
+ for (let i = 0; i < inputTx.operations.length; i++) {
62
+ const op = inputTx.operations[i];
63
+ if (op.source && op.source !== fundAddress) {
64
+ throw (0, relayer_sdk_1.pluginError)(`Operation ${i} has source ${op.source} but must be ${fundAddress} or omitted`, {
65
+ code: "INVALID_OPERATION_SOURCE",
66
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
67
+ details: {
68
+ operationIndex: i,
69
+ operationSource: op.source,
70
+ expectedSource: fundAddress,
71
+ },
72
+ });
73
+ }
74
+ }
75
+ // Manipulate the transaction envelope XDR directly to change source and sequence
76
+ // This is the most reliable approach that preserves all transaction details
77
+ const envelope = inputTx.toEnvelope();
78
+ const txBody = envelope.v1().tx();
79
+ // Update source account to channel address
80
+ const channelAccountId = stellar_sdk_1.StrKey.decodeEd25519PublicKey(channelAddress);
81
+ const channelMuxed = stellar_sdk_1.xdr.MuxedAccount.keyTypeEd25519(channelAccountId);
82
+ txBody.sourceAccount(channelMuxed);
83
+ // Update sequence number
84
+ // Sequence numbers in Stellar are represented as Int64 in XDR
85
+ const seqNum = stellar_sdk_1.xdr.Int64.fromString(channelSequence);
86
+ txBody.seqNum(seqNum);
87
+ // Update operation sources to fund address if not set
88
+ const fundAccountId = stellar_sdk_1.StrKey.decodeEd25519PublicKey(fundAddress);
89
+ const fundMuxed = stellar_sdk_1.xdr.MuxedAccount.keyTypeEd25519(fundAccountId);
90
+ const operations = txBody.operations();
91
+ for (let i = 0; i < operations.length; i++) {
92
+ const op = operations[i];
93
+ // If operation doesn't have a source, set it to fund address
94
+ if (!op.sourceAccount()) {
95
+ op.sourceAccount(fundMuxed);
96
+ }
97
+ }
98
+ // Create new transaction from modified envelope
99
+ return new stellar_sdk_1.Transaction(envelope, networkPassphrase);
100
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * config.ts
3
+ *
4
+ * Environment-driven configuration for the channel accounts plugin.
5
+ */
6
+ export interface ChannelAccountsConfig {
7
+ fundRelayerId: string;
8
+ network: "testnet" | "mainnet";
9
+ rpcUrl: string;
10
+ }
11
+ /**
12
+ * Load configuration from environment variables
13
+ */
14
+ export declare function loadConfig(): ChannelAccountsConfig;
15
+ /**
16
+ * Get the network passphrase based on the configuration
17
+ */
18
+ export declare function getNetworkPassphrase(network: "testnet" | "mainnet"): string;
19
+ /**
20
+ * Get the per-channel lock TTL in seconds (default 30)
21
+ */
22
+ export declare function getLockTtlSeconds(): number;
23
+ /**
24
+ * Get the max fee for fee bump transactions
25
+ */
26
+ export declare function getMaxFee(): number;
27
+ /**
28
+ * Get the admin secret for management API
29
+ */
30
+ export declare function getAdminSecret(): string | undefined;
31
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB;AAcD;;GAEG;AACH,wBAAgB,UAAU,IAAI,qBAAqB,CAiBlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAE3E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAY1C;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAQlC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAKnD"}