@openzeppelin/relayer-plugin-channels 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -43
- package/dist/client/channels-client.d.ts +1 -1
- package/dist/client/channels-client.d.ts.map +1 -1
- package/dist/client/channels-client.js +20 -21
- package/dist/client/errors.d.ts +1 -1
- package/dist/client/errors.js +12 -9
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/types.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/plugin/build.d.ts +1 -1
- package/dist/plugin/build.d.ts.map +1 -1
- package/dist/plugin/build.js +11 -17
- package/dist/plugin/config.d.ts +4 -2
- package/dist/plugin/config.d.ts.map +1 -1
- package/dist/plugin/config.js +43 -13
- package/dist/plugin/constants.d.ts +1 -2
- package/dist/plugin/constants.d.ts.map +1 -1
- package/dist/plugin/constants.js +2 -3
- package/dist/plugin/fee-tracking.d.ts +2 -2
- package/dist/plugin/fee-tracking.d.ts.map +1 -1
- package/dist/plugin/fee-tracking.js +4 -9
- package/dist/plugin/fee.d.ts +16 -5
- package/dist/plugin/fee.d.ts.map +1 -1
- package/dist/plugin/fee.js +52 -14
- package/dist/plugin/handler.d.ts +10 -1
- package/dist/plugin/handler.d.ts.map +1 -1
- package/dist/plugin/handler.js +87 -52
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/management.d.ts +1 -1
- package/dist/plugin/management.d.ts.map +1 -1
- package/dist/plugin/management.js +53 -79
- package/dist/plugin/pool.d.ts +8 -3
- package/dist/plugin/pool.d.ts.map +1 -1
- package/dist/plugin/pool.js +45 -17
- package/dist/plugin/simulation.d.ts +4 -2
- package/dist/plugin/simulation.d.ts.map +1 -1
- package/dist/plugin/simulation.js +41 -33
- package/dist/plugin/submit.d.ts +7 -5
- package/dist/plugin/submit.d.ts.map +1 -1
- package/dist/plugin/submit.js +23 -13
- package/dist/plugin/tx.d.ts +1 -1
- package/dist/plugin/tx.d.ts.map +1 -1
- package/dist/plugin/tx.js +6 -8
- package/dist/plugin/types.d.ts +4 -4
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/validation.d.ts +1 -1
- package/dist/plugin/validation.d.ts.map +1 -1
- package/dist/plugin/validation.js +26 -27
- package/package.json +1 -1
package/dist/plugin/config.js
CHANGED
|
@@ -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:
|
|
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
|
|
58
|
+
return 'x-api-key';
|
|
61
59
|
const trimmed = raw.trim().toLowerCase();
|
|
62
|
-
return trimmed.length > 0 ? trimmed :
|
|
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(
|
|
69
|
-
if (networkRaw !==
|
|
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:
|
|
99
|
+
code: 'UNSUPPORTED_NETWORK',
|
|
72
100
|
status: constants_1.HTTP_STATUS.BAD_REQUEST,
|
|
73
101
|
});
|
|
74
102
|
}
|
|
75
103
|
return {
|
|
76
|
-
fundRelayerId: requireEnv(
|
|
104
|
+
fundRelayerId: requireEnv('FUND_RELAYER_ID'),
|
|
77
105
|
network: networkRaw,
|
|
78
106
|
lockTtlSeconds: parseLockTtl(),
|
|
79
|
-
adminSecret: parseOptionalString(
|
|
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 ===
|
|
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
|
|
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"}
|
package/dist/plugin/constants.js
CHANGED
|
@@ -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:
|
|
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
|
|
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:
|
|
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;
|
|
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:
|
|
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:
|
|
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,
|
package/dist/plugin/fee.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fee.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - For Soroban transactions: use resourceFee +
|
|
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
|
|
9
|
-
export declare
|
|
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
|
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;;;;;;;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"}
|
package/dist/plugin/fee.js
CHANGED
|
@@ -2,15 +2,59 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* fee.ts
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* - For Soroban transactions: use resourceFee +
|
|
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
|
-
|
|
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
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
}
|
package/dist/plugin/handler.d.ts
CHANGED
|
@@ -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
|
|
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;
|
|
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"}
|
package/dist/plugin/handler.js
CHANGED
|
@@ -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
|
-
|
|
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)(
|
|
42
|
-
code:
|
|
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 !==
|
|
49
|
-
throw (0, relayer_sdk_1.pluginError)(
|
|
50
|
-
code:
|
|
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)(
|
|
86
|
-
code:
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
142
|
-
return result;
|
|
177
|
+
return channelAccounts(context);
|
|
143
178
|
}
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|