@openzeppelin/relayer-plugin-channels 0.14.0 → 0.16.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.
@@ -1 +1 @@
1
- {"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,GAAG,EAAqB,MAAM,sBAAsB,CAAC;AAG3E,MAAM,WAAW,aAAa;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,GAAG,MAAM,GAAG,SAAS,CAUhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAYzF;AASD,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAoBpH"}
1
+ {"version":3,"file":"fee.d.ts","sourceRoot":"","sources":["../../src/plugin/fee.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,GAAG,EAAqB,MAAM,sBAAsB,CAAC;AAG3E,MAAM,WAAW,aAAa;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,GAAG,MAAM,GAAG,SAAS,CAUhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAYzF;AASD,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAgCpH"}
@@ -63,7 +63,14 @@ function calculateMaxFee(transaction, limitedContracts, fees) {
63
63
  }
64
64
  const contractId = getContractIdFromTransaction(transaction);
65
65
  const inclusionFee = getInclusionFee(contractId, limitedContracts, fees);
66
- const fee = resourceFee > 0n ? resourceFee + BigInt(inclusionFee) : BigInt(constants_1.FEE.NON_SOROBAN_FEE + inclusionFee);
67
- console.debug(`[channels] Calculated max_fee: ${Number(fee)} stroops (resourceFee: ${resourceFee}, inclusionFee: ${inclusionFee})`);
66
+ const computedFee = resourceFee > 0n ? resourceFee + BigInt(inclusionFee) : BigInt(constants_1.FEE.NON_SOROBAN_FEE + inclusionFee);
67
+ const innerTxFee = BigInt(transaction.fee);
68
+ // Safety floor: fee bump max_fee must always cover the actual inner transaction fee.
69
+ // This protects against upstream fee-field mismatches that can produce a larger inner fee.
70
+ const fee = computedFee >= innerTxFee ? computedFee : innerTxFee + BigInt(inclusionFee);
71
+ if (fee !== computedFee) {
72
+ console.warn(`[channels] Fee mismatch detected: computed=${computedFee}, innerTxFee=${innerTxFee}. Using max_fee=${fee}`);
73
+ }
74
+ console.debug(`[channels] Calculated max_fee: ${Number(fee)} stroops (resourceFee: ${resourceFee}, inclusionFee: ${inclusionFee}, innerTxFee: ${innerTxFee})`);
68
75
  return Number(fee);
69
76
  }
@@ -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;AA2BxD;;;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;AA6RD;;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;AA2BxD;;;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;AAiSD;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
@@ -112,8 +112,10 @@ async function handleFuncAuthSubmit(func, auth, ctx, skipWait) {
112
112
  const sequence = await (0, sequence_1.getSequence)(ctx.kv, ctx.network, channelRelayer, channelInfo.address, ctx.config.sequenceNumberCacheMaxAgeMs);
113
113
  // Assemble the transaction using the cached simulation result — no second RPC call
114
114
  const built = (0, simulation_1.buildWithChannel)(func, auth, { address: channelInfo.address, sequence }, ctx.networkPassphrase, simulation.rawSimResult, ctx.config.minSignatureExpirationLedgerBuffer);
115
+ console.debug(`[channels] After assembly: built.fee=${built.fee}, minResourceFee=${simulation.rawSimResult.minResourceFee}`);
115
116
  const signedTx = await (0, submit_1.signWithChannelAndFund)(built, channelRelayer, ctx.fundRelayer, channelInfo.address, ctx.fundAddress, ctx.networkPassphrase);
116
117
  const maxFee = (0, fee_1.calculateMaxFee)(signedTx, ctx.acquireOptions.limitedContracts, ctx.fees);
118
+ console.debug(`[channels] After signing: signedTx.fee=${signedTx.fee}, maxFee=${maxFee}`);
117
119
  const contractId = (0, fee_1.getContractIdFromFunc)(func);
118
120
  await ctx.tracker?.checkBudget(maxFee);
119
121
  const submitContext = {
@@ -1 +1 @@
1
- {"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/plugin/simulation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,GAAG,EAAE,WAAW,EAAsB,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAgD,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGlG,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,CAAC;CACtD;AAOD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAiF3B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,OAAO,EAAE,cAAc,EACvB,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,EACjD,kCAAkC,GAAE,MAA0D,GAC7F,WAAW,CA4Cb;AA4CD,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;AAOD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAiF3B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,GAAG,CAAC,YAAY,EACtB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,GAAG,SAAS,EACjD,OAAO,EAAE,cAAc,EACvB,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,8BAA8B,EACjD,kCAAkC,GAAE,MAA0D,GAC7F,WAAW,CA+Fb;AA4CD,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAe1D"}
@@ -105,31 +105,81 @@ async function simulateTransaction(func, auth, sourceAddress, relayer, networkPa
105
105
  * obtained simulation result. No additional network calls are made.
106
106
  */
107
107
  function buildWithChannel(func, auth, channel, networkPassphrase, simResult, minSignatureExpirationLedgerBuffer = constants_1.SIMULATION.MIN_SIGNATURE_EXPIRATION_LEDGER_BUFFER) {
108
- // Fail fast: reject auth entries whose signatureExpirationLedger is too close
109
- // to latestLedger before doing any assembly work.
108
+ if (!simResult.transactionData) {
109
+ throw (0, relayer_sdk_1.pluginError)('Simulation response missing transactionData', {
110
+ code: 'SIMULATION_INVALID_RESPONSE',
111
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
112
+ });
113
+ }
114
+ // Parse sorobanData from the simulation result
115
+ let sorobanData;
116
+ try {
117
+ sorobanData = stellar_sdk_1.xdr.SorobanTransactionData.fromXDR(simResult.transactionData, 'base64');
118
+ }
119
+ catch (err) {
120
+ throw (0, relayer_sdk_1.pluginError)('Failed to parse simulation transactionData', {
121
+ code: 'SIMULATION_INVALID_RESPONSE',
122
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
123
+ details: { message: err instanceof Error ? err.message : String(err) },
124
+ });
125
+ }
126
+ // Resolve auth: use caller-provided auth if present, otherwise fall back to simulation auth
127
+ let resolvedAuth;
128
+ if (auth && auth.length > 0) {
129
+ resolvedAuth = auth;
130
+ }
131
+ else {
132
+ try {
133
+ resolvedAuth = (simResult.results?.[0]?.auth ?? []).map((a) => stellar_sdk_1.xdr.SorobanAuthorizationEntry.fromXDR(a, 'base64'));
134
+ }
135
+ catch (err) {
136
+ throw (0, relayer_sdk_1.pluginError)('Failed to parse simulation auth entries', {
137
+ code: 'SIMULATION_INVALID_RESPONSE',
138
+ status: constants_1.HTTP_STATUS.BAD_GATEWAY,
139
+ details: { message: err instanceof Error ? err.message : String(err) },
140
+ });
141
+ }
142
+ }
143
+ // Validate resolved auth expiry before doing any assembly work.
110
144
  if (simResult.latestLedger) {
111
- validateAuthExpiry(auth, simResult.latestLedger, minSignatureExpirationLedgerBuffer);
145
+ validateAuthExpiry(resolvedAuth, simResult.latestLedger, minSignatureExpirationLedgerBuffer);
112
146
  }
113
147
  const now = Math.floor(Date.now() / 1000);
114
- console.debug(`[channels] Building tx: channel=${channel.address}, seq=${channel.sequence}, auth_count=${auth?.length ?? 0}`);
115
- // Build inner transaction (source = channel)
148
+ console.debug(`[channels] Building tx: channel=${channel.address}, seq=${channel.sequence}, auth_count=${resolvedAuth.length}`);
149
+ // Build the transaction directly with sorobanData instead of using rpc.assembleTransaction().
150
+ //
151
+ // Why: stellar-base >=14.1.0 changed TransactionBuilder.build() to automatically add
152
+ // sorobanData.resourceFee() to the transaction fee (stellar/js-stellar-base#761).
153
+ // However, rpc.assembleTransaction() was not updated for this change — it still computes
154
+ // fee = classicFee + minResourceFee before calling cloneFrom/build, so build() then adds
155
+ // resourceFee a second time:
156
+ //
157
+ // assembleTransaction path (broken with >=14.1.0):
158
+ // baseFee = classicFee(100) + minResourceFee(25102) = 25202
159
+ // build() adds resourceFee(25102) → final fee = 50304 (double-counted!)
160
+ //
161
+ // Direct build path (correct):
162
+ // baseFee = classicFee(100)
163
+ // build() adds resourceFee(25102) → final fee = 25202 (correct)
164
+ //
165
+ // By passing sorobanData directly to TransactionBuilder, build() adds the resourceFee
166
+ // exactly once. This approach also removes the assembleTransaction network-format
167
+ // dependency and gives us explicit control over auth resolution.
116
168
  const transaction = new stellar_sdk_1.TransactionBuilder(new stellar_sdk_1.Account(channel.address, channel.sequence), {
117
169
  fee: constants_1.SIMULATION.DEFAULT_FEE,
118
170
  networkPassphrase,
119
171
  timebounds: { minTime: constants_1.SIMULATION.MIN_TIME_BOUND, maxTime: now + constants_1.SIMULATION.MAX_TIME_BOUND_OFFSET_SECONDS },
172
+ sorobanData,
120
173
  })
121
174
  .addOperation(stellar_sdk_1.Operation.invokeHostFunction({
122
175
  func,
123
- auth,
124
- // No explicit source: default to transaction source (channel account)
176
+ auth: resolvedAuth,
125
177
  }))
126
178
  .build();
127
179
  try {
128
- // Use SDK's assembleTransaction to apply the cached simulation results
129
- const prepared = stellar_sdk_1.rpc.assembleTransaction(transaction, simResult).build();
130
- const resourceFee = prepared.toEnvelope().v1().tx().ext().sorobanData()?.resourceFee();
131
- console.debug(`[channels] Assembly complete: resourceFee=${resourceFee}`);
132
- return prepared;
180
+ const resourceFee = transaction.toEnvelope().v1().tx().ext().sorobanData()?.resourceFee();
181
+ console.debug(`[channels] Assembly complete: fee=${transaction.fee}, resourceFee=${resourceFee}`);
182
+ return transaction;
133
183
  }
134
184
  catch (err) {
135
185
  console.error(`[channels] Assembly error: ${err instanceof Error ? err.message : String(err)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/relayer-plugin-channels",
3
- "version": "0.14.0",
3
+ "version": "0.16.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,8 +20,8 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "@actions/exec": "^1.1.1",
23
- "@openzeppelin/relayer-sdk": "^1.9.0",
24
- "@stellar/stellar-sdk": "^14.5.0",
23
+ "@openzeppelin/relayer-sdk": "^1.10.0",
24
+ "@stellar/stellar-sdk": "^14.6.0",
25
25
  "axios": "^1.13.5"
26
26
  },
27
27
  "devDependencies": {