@openzeppelin/relayer-plugin-channels 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +79 -43
  2. package/dist/client/channels-client.d.ts +1 -1
  3. package/dist/client/channels-client.d.ts.map +1 -1
  4. package/dist/client/channels-client.js +20 -21
  5. package/dist/client/errors.d.ts +1 -1
  6. package/dist/client/errors.js +12 -9
  7. package/dist/client/index.d.ts +3 -3
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/types.d.ts +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/plugin/build.d.ts +1 -1
  12. package/dist/plugin/build.d.ts.map +1 -1
  13. package/dist/plugin/build.js +11 -17
  14. package/dist/plugin/config.d.ts +4 -2
  15. package/dist/plugin/config.d.ts.map +1 -1
  16. package/dist/plugin/config.js +43 -13
  17. package/dist/plugin/constants.d.ts +1 -2
  18. package/dist/plugin/constants.d.ts.map +1 -1
  19. package/dist/plugin/constants.js +2 -3
  20. package/dist/plugin/fee-tracking.d.ts +2 -2
  21. package/dist/plugin/fee-tracking.d.ts.map +1 -1
  22. package/dist/plugin/fee-tracking.js +4 -9
  23. package/dist/plugin/fee.d.ts +16 -5
  24. package/dist/plugin/fee.d.ts.map +1 -1
  25. package/dist/plugin/fee.js +52 -14
  26. package/dist/plugin/handler.d.ts +10 -1
  27. package/dist/plugin/handler.d.ts.map +1 -1
  28. package/dist/plugin/handler.js +87 -52
  29. package/dist/plugin/index.d.ts +1 -1
  30. package/dist/plugin/management.d.ts +1 -1
  31. package/dist/plugin/management.d.ts.map +1 -1
  32. package/dist/plugin/management.js +53 -79
  33. package/dist/plugin/pool.d.ts +8 -3
  34. package/dist/plugin/pool.d.ts.map +1 -1
  35. package/dist/plugin/pool.js +45 -17
  36. package/dist/plugin/simulation.d.ts +4 -2
  37. package/dist/plugin/simulation.d.ts.map +1 -1
  38. package/dist/plugin/simulation.js +41 -33
  39. package/dist/plugin/submit.d.ts +7 -5
  40. package/dist/plugin/submit.d.ts.map +1 -1
  41. package/dist/plugin/submit.js +23 -13
  42. package/dist/plugin/tx.d.ts +1 -1
  43. package/dist/plugin/tx.d.ts.map +1 -1
  44. package/dist/plugin/tx.js +6 -8
  45. package/dist/plugin/types.d.ts +4 -4
  46. package/dist/plugin/types.d.ts.map +1 -1
  47. package/dist/plugin/validation.d.ts +1 -1
  48. package/dist/plugin/validation.d.ts.map +1 -1
  49. package/dist/plugin/validation.js +26 -27
  50. package/package.json +1 -1
@@ -12,9 +12,9 @@ const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
12
12
  const constants_1 = require("./constants");
13
13
  function requireEnv(name) {
14
14
  const v = process.env[name];
15
- if (!v || v.trim() === "") {
15
+ if (!v || v.trim() === '') {
16
16
  throw (0, relayer_sdk_1.pluginError)(`Missing required environment variable: ${name}`, {
17
- code: "CONFIG_MISSING",
17
+ code: 'CONFIG_MISSING',
18
18
  status: constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR,
19
19
  details: { name },
20
20
  });
@@ -33,9 +33,7 @@ function parseLockTtl() {
33
33
  if (!raw)
34
34
  return constants_1.CONFIG.DEFAULT_LOCK_TTL_SECONDS;
35
35
  const n = Number(raw);
36
- if (!Number.isFinite(n) ||
37
- n < constants_1.CONFIG.MIN_LOCK_TTL_SECONDS ||
38
- n > constants_1.CONFIG.MAX_LOCK_TTL_SECONDS) {
36
+ if (!Number.isFinite(n) || n < constants_1.CONFIG.MIN_LOCK_TTL_SECONDS || n > constants_1.CONFIG.MAX_LOCK_TTL_SECONDS) {
39
37
  return constants_1.CONFIG.DEFAULT_LOCK_TTL_SECONDS;
40
38
  }
41
39
  return Math.floor(n);
@@ -57,34 +55,66 @@ function parseFeeResetPeriod() {
57
55
  function parseApiKeyHeader() {
58
56
  const raw = process.env.API_KEY_HEADER;
59
57
  if (!raw)
60
- return "x-api-key";
58
+ return 'x-api-key';
61
59
  const trimmed = raw.trim().toLowerCase();
62
- return trimmed.length > 0 ? trimmed : "x-api-key";
60
+ return trimmed.length > 0 ? trimmed : 'x-api-key';
61
+ }
62
+ function parseLimitedContracts() {
63
+ const raw = process.env.LIMITED_CONTRACTS;
64
+ if (!raw)
65
+ return new Set();
66
+ const contracts = raw
67
+ .split(',')
68
+ .map((s) => s.trim().toUpperCase())
69
+ .filter((s) => s.length > 0);
70
+ // Validate each contract address
71
+ for (const contract of contracts) {
72
+ if (!stellar_sdk_1.StrKey.isValidContract(contract)) {
73
+ throw (0, relayer_sdk_1.pluginError)(`Invalid contract address in LIMITED_CONTRACTS: ${contract}`, {
74
+ code: 'CONFIG_INVALID',
75
+ status: constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR,
76
+ details: { contract },
77
+ });
78
+ }
79
+ }
80
+ return new Set(contracts);
81
+ }
82
+ function parseContractCapacityRatio() {
83
+ const raw = process.env.CONTRACT_CAPACITY_RATIO;
84
+ if (!raw)
85
+ return constants_1.CONFIG.DEFAULT_CONTRACT_CAPACITY_RATIO;
86
+ const n = Number(raw);
87
+ if (!Number.isFinite(n) || n < 0 || n > 1) {
88
+ return constants_1.CONFIG.DEFAULT_CONTRACT_CAPACITY_RATIO;
89
+ }
90
+ return n;
63
91
  }
64
92
  /**
65
93
  * Load configuration from environment variables
66
94
  */
67
95
  function loadConfig() {
68
- const networkRaw = requireEnv("STELLAR_NETWORK").toLowerCase();
69
- if (networkRaw !== "testnet" && networkRaw !== "mainnet") {
96
+ const networkRaw = requireEnv('STELLAR_NETWORK').toLowerCase();
97
+ if (networkRaw !== 'testnet' && networkRaw !== 'mainnet') {
70
98
  throw (0, relayer_sdk_1.pluginError)('STELLAR_NETWORK must be "testnet" or "mainnet"', {
71
- code: "UNSUPPORTED_NETWORK",
99
+ code: 'UNSUPPORTED_NETWORK',
72
100
  status: constants_1.HTTP_STATUS.BAD_REQUEST,
73
101
  });
74
102
  }
75
103
  return {
76
- fundRelayerId: requireEnv("FUND_RELAYER_ID"),
104
+ fundRelayerId: requireEnv('FUND_RELAYER_ID'),
77
105
  network: networkRaw,
78
106
  lockTtlSeconds: parseLockTtl(),
79
- adminSecret: parseOptionalString("PLUGIN_ADMIN_SECRET"),
107
+ adminSecret: parseOptionalString('PLUGIN_ADMIN_SECRET'),
80
108
  feeLimit: parseFeeLimit(),
81
109
  feeResetPeriodMs: parseFeeResetPeriod(),
82
110
  apiKeyHeader: parseApiKeyHeader(),
111
+ limitedContracts: parseLimitedContracts(),
112
+ contractCapacityRatio: parseContractCapacityRatio(),
83
113
  };
84
114
  }
85
115
  /**
86
116
  * Get the network passphrase based on the configuration
87
117
  */
88
118
  function getNetworkPassphrase(network) {
89
- return network === "mainnet" ? stellar_sdk_1.Networks.PUBLIC : stellar_sdk_1.Networks.TESTNET;
119
+ return network === 'mainnet' ? stellar_sdk_1.Networks.PUBLIC : stellar_sdk_1.Networks.TESTNET;
90
120
  }
@@ -18,6 +18,7 @@ export declare const CONFIG: {
18
18
  readonly DEFAULT_LOCK_TTL_SECONDS: 30;
19
19
  readonly MIN_LOCK_TTL_SECONDS: 3;
20
20
  readonly MAX_LOCK_TTL_SECONDS: 30;
21
+ readonly DEFAULT_CONTRACT_CAPACITY_RATIO: 0.8;
21
22
  };
22
23
  export declare const POOL: {
23
24
  readonly MUTEX_TTL_SECONDS: 1;
@@ -39,8 +40,6 @@ export declare const POLLING: {
39
40
  readonly TIMEOUT_MS: 25000;
40
41
  };
41
42
  export declare const FEE: {
42
- readonly MIN_BASE_FEE: 205;
43
- readonly MAX_BASE_FEE: 605;
44
43
  readonly NON_SOROBAN_FEE: 100000;
45
44
  };
46
45
  //# sourceMappingURL=constants.d.ts.map
@@ -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;;;;CAIT,CAAC;AAGX,eAAO,MAAM,IAAI;;;;;CAOP,CAAC;AAGX,eAAO,MAAM,IAAI;;CAEP,CAAC;AAGX,eAAO,MAAM,UAAU;;;;;CAKb,CAAC;AAGX,eAAO,MAAM,OAAO;;;CAGV,CAAC;AAEX,eAAO,MAAM,GAAG;;;;CAKN,CAAC"}
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;;;;;CAKT,CAAC;AAGX,eAAO,MAAM,IAAI;;;;;CAOP,CAAC;AAGX,eAAO,MAAM,IAAI;;CAEP,CAAC;AAGX,eAAO,MAAM,UAAU;;;;;CAKb,CAAC;AAGX,eAAO,MAAM,OAAO;;;CAGV,CAAC;AAEX,eAAO,MAAM,GAAG;;CAGN,CAAC"}
@@ -23,6 +23,7 @@ exports.CONFIG = {
23
23
  DEFAULT_LOCK_TTL_SECONDS: 30,
24
24
  MIN_LOCK_TTL_SECONDS: 3,
25
25
  MAX_LOCK_TTL_SECONDS: 30,
26
+ DEFAULT_CONTRACT_CAPACITY_RATIO: 0.8,
26
27
  };
27
28
  // Pool Constants
28
29
  exports.POOL = {
@@ -39,7 +40,7 @@ exports.TIME = {
39
40
  };
40
41
  // Simulation-related defaults
41
42
  exports.SIMULATION = {
42
- DEFAULT_FEE: "100",
43
+ DEFAULT_FEE: '100',
43
44
  MIN_TIME_BOUND: 0,
44
45
  MAX_TIME_BOUND_OFFSET_SECONDS: 30,
45
46
  MAX_FUTURE_TIME_BOUND_SECONDS: 30,
@@ -50,8 +51,6 @@ exports.POLLING = {
50
51
  TIMEOUT_MS: 25000,
51
52
  };
52
53
  exports.FEE = {
53
- MIN_BASE_FEE: 205,
54
- MAX_BASE_FEE: 605,
55
54
  // For non-Soroban txs: 100,000 stroops (0.01 XLM) per Stellar best practice
56
55
  NON_SOROBAN_FEE: 100000,
57
56
  };
@@ -5,7 +5,7 @@
5
5
  * Supports custom per-key limits with fallback to default.
6
6
  * Supports periodic reset of consumption.
7
7
  */
8
- import { PluginKVStore } from "@openzeppelin/relayer-sdk";
8
+ import { PluginKVStore } from '@openzeppelin/relayer-sdk';
9
9
  /** KV data structure for fee consumption */
10
10
  export interface FeeData {
11
11
  consumed: number;
@@ -22,7 +22,7 @@ export interface UsageInfo {
22
22
  /** Configuration for FeeTracker */
23
23
  export interface FeeTrackerConfig {
24
24
  kv: PluginKVStore;
25
- network: "testnet" | "mainnet";
25
+ network: 'testnet' | 'mainnet';
26
26
  apiKey: string;
27
27
  defaultLimit?: number;
28
28
  resetPeriodMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"fee-tracking.d.ts","sourceRoot":"","sources":["../../src/plugin/fee-tracking.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AAGvE,4CAA4C;AAC5C,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAQD,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,aAAa,CAAC;IAClB,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;gBAE5B,MAAM,EAAE,gBAAgB;IAUpC;;;OAGG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB7C;;;;OAIG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B7C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAqBxC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAKnD;;OAEG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAMxC;;;OAGG;YACW,WAAW;IASzB;;OAEG;YACW,iBAAiB;IAK/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,OAAO,GAElB;CACF"}
1
+ {"version":3,"file":"fee-tracking.d.ts","sourceRoot":"","sources":["../../src/plugin/fee-tracking.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AAGvE,4CAA4C;AAC5C,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAQD,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,aAAa,CAAC;IAClB,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;gBAE5B,MAAM,EAAE,gBAAgB;IAUpC;;;OAGG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB7C;;;;OAIG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B7C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAexC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAKnD;;OAEG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAMxC;;;OAGG;YACW,WAAW;IASzB;;OAEG;YACW,iBAAiB;IAK/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,OAAO,GAElB;CACF"}
@@ -31,7 +31,7 @@ class FeeTracker {
31
31
  if (consumed + fee > limit) {
32
32
  const remaining = limit - consumed;
33
33
  throw (0, relayer_sdk_1.pluginError)(`Transaction fee (${fee} stroops) exceeds remaining budget (${remaining} stroops). Consumed: ${consumed}/${limit} stroops.`, {
34
- code: "FEE_LIMIT_EXCEEDED",
34
+ code: 'FEE_LIMIT_EXCEEDED',
35
35
  status: constants_1.HTTP_STATUS.TOO_MANY_REQUESTS,
36
36
  details: { consumed, fee, remaining, limit },
37
37
  });
@@ -54,7 +54,7 @@ class FeeTracker {
54
54
  periodStart: state.periodStart ?? now,
55
55
  });
56
56
  return true;
57
- }, { ttlSec: 5, onBusy: "skip" });
57
+ }, { ttlSec: 5, onBusy: 'skip' });
58
58
  if (result !== null)
59
59
  return;
60
60
  // Lock busy, retry with jitter
@@ -71,17 +71,12 @@ class FeeTracker {
71
71
  * Get complete usage info for management API.
72
72
  */
73
73
  async getUsageInfo() {
74
- const [state, limit] = await Promise.all([
75
- this.getFeeState(),
76
- this.getEffectiveLimit(),
77
- ]);
74
+ const [state, limit] = await Promise.all([this.getFeeState(), this.getEffectiveLimit()]);
78
75
  return {
79
76
  consumed: state.consumed,
80
77
  limit,
81
78
  remaining: limit !== undefined ? Math.max(0, limit - state.consumed) : undefined,
82
- periodStartAt: state.periodStart
83
- ? new Date(state.periodStart).toISOString()
84
- : undefined,
79
+ periodStartAt: state.periodStart ? new Date(state.periodStart).toISOString() : undefined,
85
80
  periodEndsAt: state.periodStart && this.resetPeriodMs
86
81
  ? new Date(state.periodStart + this.resetPeriodMs).toISOString()
87
82
  : undefined,
@@ -1,10 +1,21 @@
1
1
  /**
2
2
  * fee.ts
3
3
  *
4
- * Dynamic fee calculation for fee bump submissions.
5
- * - For Soroban transactions: use resourceFee + random inclusion fee
6
- * - For non-Soroban: use NON_SOROBAN_FEE
4
+ * Static fee calculation for fee bump submissions matching launchtube.
5
+ * - For Soroban transactions: use resourceFee + inclusion fee (BASE_FEE * 2 + 3)
6
+ * - For non-Soroban: use NON_SOROBAN_FEE + inclusion fee
7
+ * - Limited contracts (from LIMITED_CONTRACTS env) get reduced fee (BASE_FEE * 2 + 1)
7
8
  */
8
- import { Transaction } from "@stellar/stellar-sdk";
9
- export declare function calculateMaxFee(transaction: Transaction): number;
9
+ import { Transaction, xdr } from '@stellar/stellar-sdk';
10
+ export declare const INCLUSION_FEE_DEFAULT: number;
11
+ export declare const INCLUSION_FEE_LIMITED: number;
12
+ /**
13
+ * Extract contract ID from a HostFunction (for func+auth flow)
14
+ */
15
+ export declare function getContractIdFromFunc(func: xdr.HostFunction): string | undefined;
16
+ /**
17
+ * Extract contract ID from a Transaction (for XDR flow)
18
+ */
19
+ export declare function getContractIdFromTransaction(transaction: Transaction): string | undefined;
20
+ export declare function calculateMaxFee(transaction: Transaction, limitedContracts?: Set<string>): number;
10
21
  //# sourceMappingURL=fee.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAGxD,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAsBhE"}
1
+ {"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,WAAW,EAAE,GAAG,EAAqB,MAAM,sBAAsB,CAAC;AAGrF,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAC9D,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAE9D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,GAAG,MAAM,GAAG,SAAS,CAUhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAYzF;AASD,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,MAAM,CAoB3G"}
@@ -2,15 +2,59 @@
2
2
  /**
3
3
  * fee.ts
4
4
  *
5
- * Dynamic fee calculation for fee bump submissions.
6
- * - For Soroban transactions: use resourceFee + random inclusion fee
7
- * - For non-Soroban: use NON_SOROBAN_FEE
5
+ * Static fee calculation for fee bump submissions matching launchtube.
6
+ * - For Soroban transactions: use resourceFee + inclusion fee (BASE_FEE * 2 + 3)
7
+ * - For non-Soroban: use NON_SOROBAN_FEE + inclusion fee
8
+ * - Limited contracts (from LIMITED_CONTRACTS env) get reduced fee (BASE_FEE * 2 + 1)
8
9
  */
9
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.INCLUSION_FEE_LIMITED = exports.INCLUSION_FEE_DEFAULT = void 0;
12
+ exports.getContractIdFromFunc = getContractIdFromFunc;
13
+ exports.getContractIdFromTransaction = getContractIdFromTransaction;
10
14
  exports.calculateMaxFee = calculateMaxFee;
11
15
  const stellar_sdk_1 = require("@stellar/stellar-sdk");
12
16
  const constants_1 = require("./constants");
13
- function calculateMaxFee(transaction) {
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
+ /**
20
+ * Extract contract ID from a HostFunction (for func+auth flow)
21
+ */
22
+ function getContractIdFromFunc(func) {
23
+ try {
24
+ if (func.switch() !== stellar_sdk_1.xdr.HostFunctionType.hostFunctionTypeInvokeContract()) {
25
+ return undefined;
26
+ }
27
+ const invokeContract = func.invokeContract();
28
+ return stellar_sdk_1.StrKey.encodeContract(invokeContract.contractAddress().contractId());
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ }
34
+ /**
35
+ * Extract contract ID from a Transaction (for XDR flow)
36
+ */
37
+ function getContractIdFromTransaction(transaction) {
38
+ try {
39
+ if (transaction.operations.length !== 1)
40
+ return undefined;
41
+ const op = transaction.operations[0];
42
+ if (op.type !== 'invokeHostFunction')
43
+ return undefined;
44
+ const invokeOp = op;
45
+ return getContractIdFromFunc(invokeOp.func);
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
51
+ function getInclusionFee(contractId, limitedContracts) {
52
+ if (contractId && limitedContracts.has(contractId)) {
53
+ return exports.INCLUSION_FEE_LIMITED;
54
+ }
55
+ return exports.INCLUSION_FEE_DEFAULT;
56
+ }
57
+ function calculateMaxFee(transaction, limitedContracts = new Set()) {
14
58
  const envelope = transaction.toEnvelope();
15
59
  let resourceFee = 0n;
16
60
  if (envelope.switch() === stellar_sdk_1.xdr.EnvelopeType.envelopeTypeTx()) {
@@ -19,15 +63,9 @@ function calculateMaxFee(transaction) {
19
63
  resourceFee = sorobanData.resourceFee().toBigInt();
20
64
  }
21
65
  }
22
- const baseInclusion = getRandomInt(constants_1.FEE.MIN_BASE_FEE, constants_1.FEE.MAX_BASE_FEE);
23
- const fee = resourceFee > 0n
24
- ? resourceFee + BigInt(baseInclusion)
25
- : BigInt(constants_1.FEE.NON_SOROBAN_FEE + baseInclusion);
26
- console.debug(`[channels] Calculated max_fee: ${Number(fee)} stroops (resourceFee: ${resourceFee}, baseInclusion: ${baseInclusion})`);
66
+ const contractId = getContractIdFromTransaction(transaction);
67
+ const inclusionFee = getInclusionFee(contractId, limitedContracts);
68
+ const fee = resourceFee > 0n ? resourceFee + BigInt(inclusionFee) : BigInt(constants_1.FEE.NON_SOROBAN_FEE + inclusionFee);
69
+ console.debug(`[channels] Calculated max_fee: ${Number(fee)} stroops (resourceFee: ${resourceFee}, inclusionFee: ${inclusionFee})`);
27
70
  return Number(fee);
28
71
  }
29
- function getRandomInt(min, max) {
30
- const mn = Math.ceil(min);
31
- const mx = Math.floor(max);
32
- return Math.floor(Math.random() * (mx - mn + 1)) + mn;
33
- }
@@ -4,7 +4,16 @@
4
4
  * Main handler for the channel accounts plugin.
5
5
  * Orchestrates the transaction processing pipeline using channel accounts with fee bumping.
6
6
  */
7
- import { PluginContext } from "@openzeppelin/relayer-sdk";
7
+ import { PluginContext } from '@openzeppelin/relayer-sdk';
8
+ import { Transaction, xdr } from '@stellar/stellar-sdk';
9
+ /**
10
+ * Extracts func and auth from an unsigned Soroban transaction.
11
+ * Returns null if the transaction is not a single invokeHostFunction operation.
12
+ */
13
+ export declare function extractFuncAuthFromUnsignedXdr(tx: Transaction): {
14
+ func: xdr.HostFunction;
15
+ auth: xdr.SorobanAuthorizationEntry[];
16
+ } | null;
8
17
  /**
9
18
  * Main plugin handler exported for OpenZeppelin Relayer
10
19
  */
@@ -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;AAqNvE;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAGlE"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/plugin/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AASvE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAWxD;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,WAAW,GACd;IAAE,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAA;CAAE,GAAG,IAAI,CAkB1E;AAwND;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
@@ -6,6 +6,7 @@
6
6
  * Orchestrates the transaction processing pipeline using channel accounts with fee bumping.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.extractFuncAuthFromUnsignedXdr = extractFuncAuthFromUnsignedXdr;
9
10
  exports.handler = handler;
10
11
  const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
11
12
  const pool_1 = require("./pool");
@@ -23,41 +24,77 @@ function getApiKey(headers, headerName) {
23
24
  const values = headers[headerName];
24
25
  return values?.[0]?.trim() || undefined;
25
26
  }
26
- async function handleXdrSubmit(xdrStr, fundRelayer, network, networkPassphrase, api, tracker) {
27
+ /**
28
+ * Extracts func and auth from an unsigned Soroban transaction.
29
+ * Returns null if the transaction is not a single invokeHostFunction operation.
30
+ */
31
+ function extractFuncAuthFromUnsignedXdr(tx) {
32
+ const ops = tx.operations;
33
+ if (ops.length !== 1) {
34
+ return null;
35
+ }
36
+ const envelope = tx.toEnvelope();
37
+ const rawOp = envelope.v1().tx().operations()[0].body();
38
+ if (rawOp.switch() !== stellar_sdk_1.xdr.OperationType.invokeHostFunction()) {
39
+ return null;
40
+ }
41
+ const invokeHostFn = rawOp.invokeHostFunctionOp();
42
+ return {
43
+ func: invokeHostFn.hostFunction(),
44
+ auth: invokeHostFn.auth(),
45
+ };
46
+ }
47
+ async function handleXdrSubmit(xdrStr, fundRelayer, fundAddress, network, networkPassphrase, api, pool, acquireOptions, tracker) {
27
48
  const tx = new stellar_sdk_1.Transaction(xdrStr, networkPassphrase);
49
+ // Unsigned XDR: extract func+auth and route through channel path
50
+ if (tx.signatures.length === 0) {
51
+ const extracted = extractFuncAuthFromUnsignedXdr(tx);
52
+ if (!extracted) {
53
+ throw (0, relayer_sdk_1.pluginError)('Unsigned XDR must contain exactly one invokeHostFunction operation', {
54
+ code: 'INVALID_UNSIGNED_XDR',
55
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
56
+ details: {
57
+ operationCount: tx.operations.length,
58
+ operationType: tx.operations[0]?.type,
59
+ },
60
+ });
61
+ }
62
+ console.log(`[channels] Unsigned XDR detected, extracting func+auth and routing through channel path`);
63
+ // Update acquireOptions with contractId from extracted func
64
+ const contractId = (0, fee_1.getContractIdFromFunc)(extracted.func);
65
+ const updatedOptions = { ...acquireOptions, contractId };
66
+ return handleFuncAuthSubmit(extracted.func, extracted.auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, updatedOptions, tracker);
67
+ }
28
68
  const validated = (0, tx_1.validateExistingTransactionForSubmitOnly)(tx);
29
- const maxFee = (0, fee_1.calculateMaxFee)(validated);
69
+ const maxFee = (0, fee_1.calculateMaxFee)(validated, acquireOptions.limitedContracts);
30
70
  await tracker?.checkBudget(maxFee);
31
71
  return (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, validated.toXDR(), network, maxFee, api, tracker);
32
72
  }
33
- async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, tracker) {
73
+ async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, acquireOptions, tracker) {
34
74
  let poolLock;
35
75
  try {
36
- poolLock = await pool.acquire();
76
+ poolLock = await pool.acquire(acquireOptions);
37
77
  const channelRelayer = api.useRelayer(poolLock.relayerId);
38
78
  const channelInfo = await channelRelayer.getRelayer();
39
79
  console.log(`[channels] Acquired channel: ${poolLock.relayerId}`);
40
80
  if (!channelInfo || !channelInfo.address) {
41
- throw (0, relayer_sdk_1.pluginError)("Channel relayer not found", {
42
- code: "RELAYER_UNAVAILABLE",
81
+ throw (0, relayer_sdk_1.pluginError)('Channel relayer not found', {
82
+ code: 'RELAYER_UNAVAILABLE',
43
83
  status: constants_1.HTTP_STATUS.BAD_GATEWAY,
44
84
  details: { relayerId: poolLock.relayerId },
45
85
  });
46
86
  }
47
87
  const channelStatus = await channelRelayer.getRelayerStatus();
48
- if (channelStatus.network_type !== "stellar") {
49
- throw (0, relayer_sdk_1.pluginError)("Channel relayer network type must be stellar", {
50
- code: "UNSUPPORTED_NETWORK",
88
+ if (channelStatus.network_type !== 'stellar') {
89
+ throw (0, relayer_sdk_1.pluginError)('Channel relayer network type must be stellar', {
90
+ code: 'UNSUPPORTED_NETWORK',
51
91
  status: constants_1.HTTP_STATUS.BAD_REQUEST,
52
- details: {
53
- network_type: channelStatus.network_type,
54
- relayerId: poolLock.relayerId,
55
- },
92
+ details: { network_type: channelStatus.network_type, relayerId: poolLock.relayerId },
56
93
  });
57
94
  }
58
95
  const built = await (0, simulation_1.simulateAndBuildWithChannel)(func, auth, { address: channelInfo.address, sequence: channelStatus.sequence_number }, fundAddress, fundRelayer, networkPassphrase);
59
96
  const signedTx = await (0, submit_1.signWithChannelAndFund)(built, channelRelayer, fundRelayer, channelInfo.address, fundAddress, networkPassphrase);
60
- const maxFee = (0, fee_1.calculateMaxFee)(signedTx);
97
+ const maxFee = (0, fee_1.calculateMaxFee)(signedTx, acquireOptions.limitedContracts);
61
98
  await tracker?.checkBudget(maxFee);
62
99
  return await (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, signedTx.toXDR(), network, maxFee, api, tracker);
63
100
  }
@@ -82,8 +119,8 @@ async function channelAccounts(context) {
82
119
  const apiKey = getApiKey(headers, config.apiKeyHeader);
83
120
  // If default limit is set, require API key
84
121
  if (config.feeLimit !== undefined && !apiKey) {
85
- throw (0, relayer_sdk_1.pluginError)("API key required", {
86
- code: "API_KEY_REQUIRED",
122
+ throw (0, relayer_sdk_1.pluginError)('API key required', {
123
+ code: 'API_KEY_REQUIRED',
87
124
  status: constants_1.HTTP_STATUS.BAD_REQUEST,
88
125
  });
89
126
  }
@@ -97,47 +134,45 @@ async function channelAccounts(context) {
97
134
  resetPeriodMs: config.feeResetPeriodMs,
98
135
  });
99
136
  }
100
- try {
101
- // 1. Validate and parse request (xdr OR func+auth)
102
- const request = (0, validation_1.validateAndParseRequest)(params);
103
- console.debug(`[channels] Request type: ${request.type}, auth entries: ${request.type === "func-auth" ? request.auth.length : "N/A"}`);
104
- // 2. Get fund relayer
105
- const fundRelayer = api.useRelayer(config.fundRelayerId);
106
- const fundInfo = await fundRelayer.getRelayer();
107
- if (!fundInfo || !fundInfo.address) {
108
- throw (0, relayer_sdk_1.pluginError)("Fund relayer not found", {
109
- code: "RELAYER_UNAVAILABLE",
110
- status: constants_1.HTTP_STATUS.BAD_GATEWAY,
111
- details: { relayerId: config.fundRelayerId },
112
- });
113
- }
114
- const fundStatus = await fundRelayer.getRelayerStatus();
115
- if (fundStatus.network_type !== "stellar") {
116
- throw (0, relayer_sdk_1.pluginError)("Fund relayer network type must be stellar", {
117
- code: "UNSUPPORTED_NETWORK",
118
- status: constants_1.HTTP_STATUS.BAD_REQUEST,
119
- details: {
120
- network_type: fundStatus.network_type,
121
- relayerId: config.fundRelayerId,
122
- },
123
- });
124
- }
125
- // 3. Branch by request type
126
- if (request.type === "xdr") {
127
- console.log(`[channels] Flow: XDR submit-only`);
128
- return await handleXdrSubmit(request.xdr, fundRelayer, config.network, networkPassphrase, api, tracker);
129
- }
130
- console.log(`[channels] Flow: func+auth with channel account`);
131
- return await handleFuncAuthSubmit(request.func, request.auth, api, pool, fundRelayer, fundInfo.address, config.network, networkPassphrase, tracker);
137
+ // 1. Validate and parse request (xdr OR func+auth)
138
+ const request = (0, validation_1.validateAndParseRequest)(params);
139
+ console.debug(`[channels] Request type: ${request.type}, auth entries: ${request.type === 'func-auth' ? request.auth.length : 'N/A'}`);
140
+ // 2. Get fund relayer
141
+ const fundRelayer = api.useRelayer(config.fundRelayerId);
142
+ const fundInfo = await fundRelayer.getRelayer();
143
+ if (!fundInfo || !fundInfo.address) {
144
+ throw (0, relayer_sdk_1.pluginError)('Fund relayer not found', {
145
+ code: 'RELAYER_UNAVAILABLE',
146
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
147
+ details: { relayerId: config.fundRelayerId },
148
+ });
132
149
  }
133
- finally {
134
- // Nothing to cleanup here; func-auth path releases locks internally
150
+ if (fundInfo.network_type !== 'stellar') {
151
+ throw (0, relayer_sdk_1.pluginError)('Fund relayer network type must be stellar', {
152
+ code: 'UNSUPPORTED_NETWORK',
153
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
154
+ details: { network_type: fundInfo.network_type, relayerId: config.fundRelayerId },
155
+ });
156
+ }
157
+ // 3. Build acquire options for contract capacity limits
158
+ const acquireOptions = {
159
+ limitedContracts: config.limitedContracts,
160
+ capacityRatio: config.contractCapacityRatio,
161
+ };
162
+ // 4. Branch by request type
163
+ if (request.type === 'xdr') {
164
+ console.log(`[channels] Flow: XDR submit-only`);
165
+ return await handleXdrSubmit(request.xdr, fundRelayer, fundInfo.address, config.network, networkPassphrase, api, pool, acquireOptions, tracker);
135
166
  }
167
+ // Extract contractId for func+auth flow
168
+ const contractId = (0, fee_1.getContractIdFromFunc)(request.func);
169
+ const funcAcquireOptions = { ...acquireOptions, contractId };
170
+ console.log(`[channels] Flow: func+auth with channel account`);
171
+ return await handleFuncAuthSubmit(request.func, request.auth, api, pool, fundRelayer, fundInfo.address, config.network, networkPassphrase, funcAcquireOptions, tracker);
136
172
  }
137
173
  /**
138
174
  * Main plugin handler exported for OpenZeppelin Relayer
139
175
  */
140
176
  async function handler(context) {
141
- const result = await channelAccounts(context);
142
- return result;
177
+ return channelAccounts(context);
143
178
  }
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Exports the main handler function for the Channels plugin
5
5
  */
6
- export { handler } from "./handler";
6
+ export { handler } from './handler';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -9,7 +9,7 @@
9
9
  * - setFeeLimit: sets custom limit for an API key
10
10
  * - deleteFeeLimit: removes custom limit for an API key
11
11
  */
12
- import type { PluginContext } from "@openzeppelin/relayer-sdk";
12
+ import type { PluginContext } from '@openzeppelin/relayer-sdk';
13
13
  export declare function isManagementRequest(params: any): boolean;
14
14
  export declare function handleManagement(context: PluginContext): Promise<any>;
15
15
  //# sourceMappingURL=management.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"management.d.ts","sourceRoot":"","sources":["../../src/plugin/management.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,2BAA2B,CAAC;AAM9E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAOxD;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CA8C3E"}
1
+ {"version":3,"file":"management.d.ts","sourceRoot":"","sources":["../../src/plugin/management.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,2BAA2B,CAAC;AAM9E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAIxD;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAkC3E"}