@pushchain/core 6.0.22 → 6.0.24

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ @pushchain/core@6.0.23 (2026-09-01)
2
+
3
+ - docs: changelog entry for the EVM PC20 burn routing fix
4
+ - fix: route EVM PC20 imports through the payload path
5
+
6
+ ---
7
+
1
8
  @pushchain/core@6.0.22 (2026-08-14)
2
9
 
3
10
  - fix: make PC20 first exports and Solana R3 E2E reliable
@@ -63,6 +70,16 @@
63
70
  metadata, and added SVM remaining-account support required by the Solana
64
71
  gateway.
65
72
 
73
+ - Fixed EVM PC20 wrapper burns destroying the wrapper without unlocking the
74
+ canonical token. A payload-less burn took the funds-only path, whose empty
75
+ payload gains the gateway's PC20 selector and then fails the chain's inbound
76
+ decode at ballot finalization — no handler runs, no revert is produced, and
77
+ the already-burned wrapper leaves the canonical token locked in VaultPC20.
78
+ The always-forward mitigation was gated to Solana; PC20 imports now route
79
+ through the payload path on both VMs, and `executeFundsOnly` fails closed
80
+ with `PC20UnsafeEmptyPayloadError` if a PC20 import ever reaches it again.
81
+ PRC20 and native funds-only flows are unchanged.
82
+
66
83
  ---
67
84
 
68
85
  @pushchain/core@6.0.20 (2026-07-10)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushchain/core",
3
- "version": "6.0.22",
3
+ "version": "6.0.24",
4
4
  "homepage": "https://push.org",
5
5
  "keywords": [
6
6
  "pushchain",
@@ -31,11 +31,19 @@ const response_builder_1 = require("./response-builder");
31
31
  const push_chain_tx_1 = require("./push-chain-tx");
32
32
  const svm_helpers_1 = require("./svm-helpers");
33
33
  const svm_1 = require("./pc20/svm");
34
+ const gate_1 = require("./pc20/gate");
34
35
  const helpers_2 = require("./helpers");
35
36
  const response_builder_2 = require("./response-builder");
36
37
  const formatters_1 = require("../../formatters");
37
38
  async function executeFundsOnly(ctx, execute, eventBuffer, getResponseCallbacks) {
38
39
  const transformFn = (tx, buf = []) => (0, response_builder_2.transformToUniversalTxResponse)(ctx, tx, buf, getResponseCallbacks());
40
+ // Fail closed: a PC20 wrapper burn must never be broadcast from this path.
41
+ // It sends an empty payload, the gateway prepends the PC20 selector, and the
42
+ // chain's inbound decode hard-fails on the selector-only result with no
43
+ // revert — burning the wrapper and locking the canonical token forever.
44
+ // The orchestrator routes PC20 imports to the payload path; this is the
45
+ // backstop for any caller that reaches here anyway.
46
+ (0, gate_1.assertPC20ImportHasPayload)(execute, false, 'executeFundsOnly');
39
47
  const chain = ctx.universalSigner.account.chain;
40
48
  const { vm } = chain_1.CHAIN_INFO[chain];
41
49
  if (!helpers_1.SUPPORTED_GATEWAY_CHAINS.includes(chain)) {
@@ -266,8 +266,8 @@ class Orchestrator {
266
266
  // Single PC20 gate. Downstream builders only ever see the legacy
267
267
  // PRC20/native shape; the PC20 branch is added here in Phase 2.
268
268
  const legacy = await (0, gate_1.gateFunds)(this.ctx, execute);
269
- // SVM PC20 imports never take the raw funds-only path: the Solana
270
- // gateway prepends the PC20 selector to the (empty) payload and
269
+ // PC20 imports never take the raw funds-only path, on EITHER VM.
270
+ // The gateway prepends the PC20 selector to the (empty) payload and
271
271
  // classifies the RESULT as FUNDS_AND_PAYLOAD, and the chain's
272
272
  // inbound decode hard-fails on that selector-only payload with no
273
273
  // revert — the wrapper is already burned (same "0x" vs "" guard as
@@ -275,9 +275,18 @@ class Orchestrator {
275
275
  // payload (UEA multicall transfer to `to`), which classifies and
276
276
  // decodes legitimately — the same always-forward mitigation R3
277
277
  // ships on both VMs.
278
- const svmPc20Import = legacy._pc20?.direction === 'import' &&
279
- chain_1.CHAIN_INFO[this.ctx.universalSigner.account.chain].vm === enums_1.VM.SVM;
280
- return attachPerCallHookForWait(!hasData && !svmPc20Import
278
+ //
279
+ // This was previously gated on VM.SVM, which left EVM wrapper burns
280
+ // on the unsafe path: observed on Donut as a burned wrapper whose
281
+ // canonical token was never unlocked. The brick is at ballot
282
+ // finalization — NormalizeForTxType (x/uexecutor/types/inbound.go)
283
+ // strips the selector, gets '0x', which passes its `!= ""` empty
284
+ // check, and DecodeRawPayload('0x') then fails — so no handler runs
285
+ // at all: no deploy, no credit, and no INBOUND_REVERT. With a real
286
+ // forward payload the decode succeeds and the funds_and_payload
287
+ // handler deploys the UEA and credits normally.
288
+ const pc20Import = legacy._pc20?.direction === 'import';
289
+ return attachPerCallHookForWait(!hasData && !pc20Import
281
290
  ? await (0, internals_1.executeFundsOnly)(this.ctx, legacy, eventBuffer, rcb)
282
291
  : await (0, internals_1.executeFundsWithPayload)(this.ctx, legacy, eventBuffer, rcb));
283
292
  }