@openzeppelin/relayer-plugin-channels 0.7.0 → 0.9.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.
@@ -62,6 +62,10 @@ export interface ChannelsTransactionResponse {
62
62
  hash: string | null;
63
63
  /** Transaction status */
64
64
  status: string | null;
65
+ /** Present only for read-only calls: the base64-encoded xdr.ScVal return value */
66
+ returnValue?: string;
67
+ /** Present only for read-only calls: the latest ledger at simulation time */
68
+ latestLedger?: number;
65
69
  /** Optional metadata (logs and traces) */
66
70
  metadata?: {
67
71
  logs?: LogEntry[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,sCAAsC;IACtC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gCAAgC;IAChC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,yBAAyB;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,oEAAoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,0CAA0C;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,qBAAqB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/plugin/build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAe,MAAM,sBAAsB,CAAC;AAIhE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CA2FrE"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/plugin/build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAe,MAAM,sBAAsB,CAAC;AAIhE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAmGrE"}
@@ -45,6 +45,13 @@ function rebuildWithChannel(params) {
45
45
  const now = Math.floor(Date.now() / 1000);
46
46
  const maxTime = Number(inputTx.timeBounds.maxTime);
47
47
  if (maxTime > 0) {
48
+ if (maxTime < now) {
49
+ throw (0, relayer_sdk_1.pluginError)('Transaction has expired: maxTime is in the past', {
50
+ code: 'TIMEBOUNDS_EXPIRED',
51
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
52
+ details: { maxTime, now },
53
+ });
54
+ }
48
55
  const maxAllowedTime = now + constants_1.TIME.MAX_TIME_BOUND_OFFSET_SECONDS;
49
56
  if (maxTime > maxAllowedTime) {
50
57
  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`, {
@@ -27,13 +27,13 @@ export declare const POOL: {
27
27
  readonly MUTEX_RETRY_MAX_MS: 30;
28
28
  };
29
29
  export declare const TIME: {
30
- readonly MAX_TIME_BOUND_OFFSET_SECONDS: 30;
30
+ readonly MAX_TIME_BOUND_OFFSET_SECONDS: 120;
31
31
  };
32
32
  export declare const SIMULATION: {
33
33
  readonly DEFAULT_FEE: "100";
34
34
  readonly MIN_TIME_BOUND: 0;
35
- readonly MAX_TIME_BOUND_OFFSET_SECONDS: 30;
36
- readonly MAX_FUTURE_TIME_BOUND_SECONDS: 30;
35
+ readonly MAX_TIME_BOUND_OFFSET_SECONDS: 120;
36
+ readonly MAX_FUTURE_TIME_BOUND_SECONDS: 120;
37
37
  };
38
38
  export declare const POLLING: {
39
39
  readonly INTERVAL_MS: 1000;
@@ -36,14 +36,14 @@ exports.POOL = {
36
36
  };
37
37
  // Time Constants
38
38
  exports.TIME = {
39
- MAX_TIME_BOUND_OFFSET_SECONDS: 30,
39
+ MAX_TIME_BOUND_OFFSET_SECONDS: 120,
40
40
  };
41
41
  // Simulation-related defaults
42
42
  exports.SIMULATION = {
43
43
  DEFAULT_FEE: '100',
44
44
  MIN_TIME_BOUND: 0,
45
- MAX_TIME_BOUND_OFFSET_SECONDS: 30,
46
- MAX_FUTURE_TIME_BOUND_SECONDS: 30,
45
+ MAX_TIME_BOUND_OFFSET_SECONDS: 120,
46
+ MAX_FUTURE_TIME_BOUND_SECONDS: 120,
47
47
  };
48
48
  // Polling for transactionWait
49
49
  exports.POLLING = {
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/plugin/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AASvE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;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"}
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;AAsOD;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
@@ -71,6 +71,18 @@ async function handleXdrSubmit(xdrStr, fundRelayer, fundAddress, network, networ
71
71
  return (0, submit_1.submitWithFeeBumpAndWait)(fundRelayer, validated.toXDR(), network, maxFee, api, tracker);
72
72
  }
73
73
  async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddress, network, networkPassphrase, acquireOptions, tracker) {
74
+ // Simulate once — used for both read-only detection and transaction assembly
75
+ const simulation = await (0, simulation_1.simulateTransaction)(func, auth, fundAddress, fundRelayer, networkPassphrase);
76
+ if (simulation.isReadOnly) {
77
+ console.log(`[channels] Read-only call detected, returning simulation result`);
78
+ return {
79
+ transactionId: null,
80
+ status: 'readonly',
81
+ hash: null,
82
+ returnValue: simulation.returnValue,
83
+ latestLedger: simulation.latestLedger,
84
+ };
85
+ }
74
86
  let poolLock;
75
87
  try {
76
88
  poolLock = await pool.acquire(acquireOptions);
@@ -92,7 +104,8 @@ async function handleFuncAuthSubmit(func, auth, api, pool, fundRelayer, fundAddr
92
104
  details: { network_type: channelStatus.network_type, relayerId: poolLock.relayerId },
93
105
  });
94
106
  }
95
- const built = await (0, simulation_1.simulateAndBuildWithChannel)(func, auth, { address: channelInfo.address, sequence: channelStatus.sequence_number }, fundAddress, fundRelayer, networkPassphrase);
107
+ // Assemble the transaction using the cached simulation result no second RPC call
108
+ const built = (0, simulation_1.buildWithChannel)(func, auth, { address: channelInfo.address, sequence: channelStatus.sequence_number }, networkPassphrase, simulation.rawSimResult);
96
109
  const signedTx = await (0, submit_1.signWithChannelAndFund)(built, channelRelayer, fundRelayer, channelInfo.address, fundAddress, networkPassphrase);
97
110
  const maxFee = (0, fee_1.calculateMaxFee)(signedTx, acquireOptions.limitedContracts);
98
111
  await tracker?.checkBudget(maxFee);
@@ -1,17 +1,45 @@
1
1
  /**
2
2
  * simulation.ts
3
3
  *
4
- * Build and simulate a Soroban transaction using a channel account as the source
5
- * and the fund account as the operation source. Returns an unsigned inner
6
- * transaction with sorobanData and correct fee set to the resource fee.
4
+ * Simulate a Soroban transaction once (using a throwaway source account) and
5
+ * reuse the result to both detect read-only calls and assemble the final
6
+ * transaction with the real channel account. This avoids a redundant network
7
+ * round-trip to the RPC node.
7
8
  */
8
- import { Transaction, xdr } from '@stellar/stellar-sdk';
9
+ import { rpc, Transaction, xdr } from '@stellar/stellar-sdk';
9
10
  import { Relayer } from '@openzeppelin/relayer-sdk';
10
11
  export interface ChannelAccount {
11
12
  address: string;
12
13
  sequence: string;
13
14
  }
14
- export declare function simulateAndBuildWithChannel(func: xdr.HostFunction, auth: xdr.SorobanAuthorizationEntry[] | undefined, channel: ChannelAccount, _fundAddress: string, relayer: Relayer, networkPassphrase: string): Promise<Transaction>;
15
+ export interface SimulationResult {
16
+ /** Whether the call is read-only (no auth + no read-write footprint). */
17
+ isReadOnly: boolean;
18
+ /** base64-encoded xdr.ScVal return value, present when isReadOnly is true. */
19
+ returnValue?: string;
20
+ /** Latest ledger at simulation time. */
21
+ latestLedger?: number;
22
+ /** Raw simulation response — used by buildWithChannel to assemble the tx. */
23
+ rawSimResult: rpc.Api.RawSimulateTransactionResponse;
24
+ }
25
+ /**
26
+ * Simulate a transaction to obtain the full simulation response and detect
27
+ * whether the call is read-only.
28
+ *
29
+ * Uses a throwaway source account (sequence "0") because `simulateTransaction`
30
+ * does not validate sequence numbers. This lets us simulate before acquiring a
31
+ * channel pool slot.
32
+ *
33
+ * A call is read-only when:
34
+ * 1. Zero auth entries — no one needs to authorize anything
35
+ * 2. Zero read-write footprint entries — no ledger state will be modified
36
+ */
37
+ export declare function simulateTransaction(func: xdr.HostFunction, auth: xdr.SorobanAuthorizationEntry[] | undefined, sourceAddress: string, relayer: Relayer, networkPassphrase: string): Promise<SimulationResult>;
38
+ /**
39
+ * Build and assemble a transaction using a channel account and an already-
40
+ * obtained simulation result. No additional network calls are made.
41
+ */
42
+ export declare function buildWithChannel(func: xdr.HostFunction, auth: xdr.SorobanAuthorizationEntry[] | undefined, channel: ChannelAccount, networkPassphrase: string, simResult: rpc.Api.RawSimulateTransactionResponse): Transaction;
15
43
  /** Extract human-readable message + error type from simulation error diagnostic events */
16
44
  export declare function parseSimulationError(error: string): string;
17
45
  //# sourceMappingURL=simulation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/plugin/simulation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA2B,WAAW,EAAsB,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAgD,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGlG,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,WAAW,CAAC,CAkFtB;AAED,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAe1D"}
1
+ {"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/plugin/simulation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,GAAG,EAAE,WAAW,EAAsB,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAgD,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGlG,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,CAAC;CACtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,gBAAgB,CAAC,CA8E3B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,OAAO,EAAE,cAAc,EACvB,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,GAChD,WAAW,CAsCb;AACD,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAe1D"}
@@ -2,30 +2,38 @@
2
2
  /**
3
3
  * simulation.ts
4
4
  *
5
- * Build and simulate a Soroban transaction using a channel account as the source
6
- * and the fund account as the operation source. Returns an unsigned inner
7
- * transaction with sorobanData and correct fee set to the resource fee.
5
+ * Simulate a Soroban transaction once (using a throwaway source account) and
6
+ * reuse the result to both detect read-only calls and assemble the final
7
+ * transaction with the real channel account. This avoids a redundant network
8
+ * round-trip to the RPC node.
8
9
  */
9
10
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.simulateAndBuildWithChannel = simulateAndBuildWithChannel;
11
+ exports.simulateTransaction = simulateTransaction;
12
+ exports.buildWithChannel = buildWithChannel;
11
13
  exports.parseSimulationError = parseSimulationError;
12
14
  const stellar_sdk_1 = require("@stellar/stellar-sdk");
13
15
  const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
14
16
  const constants_1 = require("./constants");
15
- async function simulateAndBuildWithChannel(func, auth, channel, _fundAddress, relayer, networkPassphrase) {
17
+ /**
18
+ * Simulate a transaction to obtain the full simulation response and detect
19
+ * whether the call is read-only.
20
+ *
21
+ * Uses a throwaway source account (sequence "0") because `simulateTransaction`
22
+ * does not validate sequence numbers. This lets us simulate before acquiring a
23
+ * channel pool slot.
24
+ *
25
+ * A call is read-only when:
26
+ * 1. Zero auth entries — no one needs to authorize anything
27
+ * 2. Zero read-write footprint entries — no ledger state will be modified
28
+ */
29
+ async function simulateTransaction(func, auth, sourceAddress, relayer, networkPassphrase) {
16
30
  const now = Math.floor(Date.now() / 1000);
17
- console.debug(`[channels] Building tx: channel=${channel.address}, seq=${channel.sequence}, auth_count=${auth?.length ?? 0}`);
18
- // Build inner transaction (source = channel, op source = fund)
19
- const transaction = new stellar_sdk_1.TransactionBuilder(new stellar_sdk_1.Account(channel.address, channel.sequence), {
31
+ const transaction = new stellar_sdk_1.TransactionBuilder(new stellar_sdk_1.Account(sourceAddress, '0'), {
20
32
  fee: constants_1.SIMULATION.DEFAULT_FEE,
21
33
  networkPassphrase,
22
34
  timebounds: { minTime: constants_1.SIMULATION.MIN_TIME_BOUND, maxTime: now + constants_1.SIMULATION.MAX_TIME_BOUND_OFFSET_SECONDS },
23
35
  })
24
- .addOperation(stellar_sdk_1.Operation.invokeHostFunction({
25
- func,
26
- auth,
27
- // No explicit source: default to transaction source (channel account)
28
- }))
36
+ .addOperation(stellar_sdk_1.Operation.invokeHostFunction({ func, auth }))
29
37
  .build();
30
38
  let rpcResponse;
31
39
  try {
@@ -33,18 +41,14 @@ async function simulateAndBuildWithChannel(func, auth, channel, _fundAddress, re
33
41
  jsonrpc: '2.0',
34
42
  id: Math.floor(Math.random() * 1e8).toString(),
35
43
  method: 'simulateTransaction',
36
- params: {
37
- transaction: transaction.toXDR(),
38
- },
44
+ params: { transaction: transaction.toXDR() },
39
45
  });
40
46
  }
41
47
  catch (err) {
42
48
  throw (0, relayer_sdk_1.pluginError)('Simulation network request failed', {
43
49
  code: 'SIMULATION_NETWORK_ERROR',
44
50
  status: constants_1.HTTP_STATUS.BAD_GATEWAY,
45
- details: {
46
- message: err instanceof Error ? err.message : String(err),
47
- },
51
+ details: { message: err instanceof Error ? err.message : String(err) },
48
52
  });
49
53
  }
50
54
  if (rpcResponse.error) {
@@ -68,17 +72,61 @@ async function simulateAndBuildWithChannel(func, auth, channel, _fundAddress, re
68
72
  details: { error: parseSimulationError(simResult.error) },
69
73
  });
70
74
  }
75
+ // Read-only detection
76
+ const results = simResult.results;
77
+ const hasAuth = (results?.[0]?.auth?.length ?? 0) > 0;
78
+ let hasReadWrite = false;
79
+ if (simResult.transactionData) {
80
+ try {
81
+ const sorobanData = stellar_sdk_1.xdr.SorobanTransactionData.fromXDR(simResult.transactionData, 'base64');
82
+ hasReadWrite = sorobanData.resources().footprint().readWrite().length > 0;
83
+ }
84
+ catch {
85
+ // If we can't parse transactionData, treat as not read-only (safe fallback)
86
+ hasReadWrite = true;
87
+ }
88
+ }
89
+ const isReadOnly = !hasAuth && !hasReadWrite;
90
+ if (isReadOnly) {
91
+ console.log(`[channels] Read-only call detected via simulation`);
92
+ }
93
+ return {
94
+ isReadOnly,
95
+ returnValue: isReadOnly ? results?.[0]?.xdr : undefined,
96
+ latestLedger: simResult.latestLedger,
97
+ rawSimResult: simResult,
98
+ };
99
+ }
100
+ /**
101
+ * Build and assemble a transaction using a channel account and an already-
102
+ * obtained simulation result. No additional network calls are made.
103
+ */
104
+ function buildWithChannel(func, auth, channel, networkPassphrase, simResult) {
105
+ const now = Math.floor(Date.now() / 1000);
106
+ console.debug(`[channels] Building tx: channel=${channel.address}, seq=${channel.sequence}, auth_count=${auth?.length ?? 0}`);
107
+ // Build inner transaction (source = channel)
108
+ const transaction = new stellar_sdk_1.TransactionBuilder(new stellar_sdk_1.Account(channel.address, channel.sequence), {
109
+ fee: constants_1.SIMULATION.DEFAULT_FEE,
110
+ networkPassphrase,
111
+ timebounds: { minTime: constants_1.SIMULATION.MIN_TIME_BOUND, maxTime: now + constants_1.SIMULATION.MAX_TIME_BOUND_OFFSET_SECONDS },
112
+ })
113
+ .addOperation(stellar_sdk_1.Operation.invokeHostFunction({
114
+ func,
115
+ auth,
116
+ // No explicit source: default to transaction source (channel account)
117
+ }))
118
+ .build();
71
119
  try {
72
- // Use SDK's assembleTransaction to apply simulation results
120
+ // Use SDK's assembleTransaction to apply the cached simulation results
73
121
  const prepared = stellar_sdk_1.rpc.assembleTransaction(transaction, simResult).build();
74
122
  const resourceFee = prepared.toEnvelope().v1().tx().ext().sorobanData()?.resourceFee();
75
- console.debug(`[channels] Simulation complete: resourceFee=${resourceFee}`);
123
+ console.debug(`[channels] Assembly complete: resourceFee=${resourceFee}`);
76
124
  return prepared;
77
125
  }
78
126
  catch (err) {
79
127
  console.error(`[channels] Assembly error: ${err instanceof Error ? err.message : String(err)}`);
80
- throw (0, relayer_sdk_1.pluginError)('Simulation failed', {
81
- code: 'SIMULATION_FAILED',
128
+ throw (0, relayer_sdk_1.pluginError)('Transaction assembly failed', {
129
+ code: 'ASSEMBLY_FAILED',
82
130
  status: constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR,
83
131
  details: {
84
132
  message: err instanceof Error ? err.message : String(err),
@@ -1 +1 @@
1
- {"version":3,"file":"tx.d.ts","sourceRoot":"","sources":["../../src/plugin/tx.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAIxD,wBAAgB,wCAAwC,CAAC,EAAE,EAAE,WAAW,GAAG,WAAW,CAsCrF"}
1
+ {"version":3,"file":"tx.d.ts","sourceRoot":"","sources":["../../src/plugin/tx.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAIxD,wBAAgB,wCAAwC,CAAC,EAAE,EAAE,WAAW,GAAG,WAAW,CAkDrF"}
package/dist/plugin/tx.js CHANGED
@@ -33,11 +33,21 @@ function validateExistingTransactionForSubmitOnly(tx) {
33
33
  }
34
34
  }
35
35
  // Time bounds sanity
36
- if (tx.timeBounds?.maxTime && Number(tx.timeBounds.maxTime) - now > constants_1.SIMULATION.MAX_FUTURE_TIME_BOUND_SECONDS) {
37
- throw (0, relayer_sdk_1.pluginError)(`Transaction \`timeBounds.maxTime\` too far into the future. Must be no greater than ${constants_1.SIMULATION.MAX_FUTURE_TIME_BOUND_SECONDS} seconds`, {
38
- code: 'TIMEBOUNDS_TOO_FAR',
39
- status: constants_1.HTTP_STATUS.BAD_REQUEST,
40
- });
36
+ if (tx.timeBounds?.maxTime && Number(tx.timeBounds.maxTime) > 0) {
37
+ const maxTime = Number(tx.timeBounds.maxTime);
38
+ if (maxTime < now) {
39
+ throw (0, relayer_sdk_1.pluginError)('Transaction has expired: `timeBounds.maxTime` is in the past', {
40
+ code: 'TIMEBOUNDS_EXPIRED',
41
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
42
+ details: { maxTime, now },
43
+ });
44
+ }
45
+ if (maxTime - now > constants_1.SIMULATION.MAX_FUTURE_TIME_BOUND_SECONDS) {
46
+ throw (0, relayer_sdk_1.pluginError)(`Transaction \`timeBounds.maxTime\` too far into the future. Must be no greater than ${constants_1.SIMULATION.MAX_FUTURE_TIME_BOUND_SECONDS} seconds`, {
47
+ code: 'TIMEBOUNDS_TOO_FAR',
48
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
49
+ });
50
+ }
41
51
  }
42
52
  return tx;
43
53
  }
@@ -24,6 +24,10 @@ export interface ChannelAccountsResponse {
24
24
  transactionId: string | null;
25
25
  status: string | null;
26
26
  hash: string | null;
27
+ /** Present only for read-only calls: the base64-encoded xdr.ScVal return value */
28
+ returnValue?: string;
29
+ /** Present only for read-only calls: the latest ledger at simulation time */
30
+ latestLedger?: number;
27
31
  }
28
32
  /**
29
33
  * Management request structure
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/plugin/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAA;CAAE,CAAC;AAEzF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;QACrD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,OAAO,CAAC;IACZ,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/plugin/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAA;CAAE,CAAC;AAEzF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;QACrD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,OAAO,CAAC;IACZ,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/relayer-plugin-channels",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "OpenZeppelin Relayer Plugin for Stellar Channel Accounts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,22 +20,22 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "@actions/exec": "^1.1.1",
23
- "@openzeppelin/relayer-sdk": "^1.8.0",
24
- "@stellar/stellar-sdk": "^14.4.0",
25
- "axios": "^1.13.2"
23
+ "@openzeppelin/relayer-sdk": "^1.9.0",
24
+ "@stellar/stellar-sdk": "^14.5.0",
25
+ "axios": "^1.13.5"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@changesets/cli": "^2.29.8",
29
- "@types/node": "^22.19.1",
30
- "@typescript-eslint/eslint-plugin": "^8.48.1",
31
- "@typescript-eslint/parser": "^8.48.1",
32
- "eslint": "^9.39.1",
29
+ "@types/node": "^22.19.11",
30
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
31
+ "@typescript-eslint/parser": "^8.55.0",
32
+ "eslint": "^9.39.2",
33
33
  "eslint-config-prettier": "^9.1.2",
34
- "eslint-plugin-prettier": "^5.5.4",
35
- "prettier": "^3.7.4",
34
+ "eslint-plugin-prettier": "^5.5.5",
35
+ "prettier": "^3.8.1",
36
36
  "typescript": "5.8.3",
37
37
  "typescript-eslint": "8.27.0",
38
- "vitest": "^4.0.15"
38
+ "vitest": "^4.0.18"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=22.18.0",