@pushchain/core 6.0.19 → 6.0.21

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 (112) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +1 -1
  3. package/src/lib/cache/cache.js +2 -0
  4. package/src/lib/constants/abi/index.d.ts +1 -0
  5. package/src/lib/constants/abi/index.js +8 -1
  6. package/src/lib/constants/abi/pc20.evm.d.ts +217 -0
  7. package/src/lib/constants/abi/pc20.evm.js +150 -0
  8. package/src/lib/constants/abi/prc20.evm.d.ts +172 -0
  9. package/src/lib/constants/abi/prc20.evm.js +97 -0
  10. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  11. package/src/lib/constants/chain.js +38 -8
  12. package/src/lib/constants/selectors.js +2 -2
  13. package/src/lib/constants/tokens.js +4 -3
  14. package/src/lib/formatters.js +16 -6
  15. package/src/lib/generated/uexecutor/v1/query.js +2 -4
  16. package/src/lib/generated/uexecutor/v1/types.d.ts +14 -0
  17. package/src/lib/generated/uexecutor/v1/types.js +90 -47
  18. package/src/lib/generated/uexecutor/v2/query.js +2 -4
  19. package/src/lib/generated/uexecutor/v2/types.d.ts +20 -0
  20. package/src/lib/generated/uexecutor/v2/types.js +49 -33
  21. package/src/lib/generated/v1/tx.js +30 -37
  22. package/src/lib/index.d.ts +4 -1
  23. package/src/lib/index.js +21 -3
  24. package/src/lib/internal/buffer.js +1 -2
  25. package/src/lib/orchestrator/cea-utils.js +73 -83
  26. package/src/lib/orchestrator/internals/account-manager.js +130 -139
  27. package/src/lib/orchestrator/internals/cascade.d.ts +0 -6
  28. package/src/lib/orchestrator/internals/cascade.js +1184 -1059
  29. package/src/lib/orchestrator/internals/confirmation.js +138 -148
  30. package/src/lib/orchestrator/internals/error-decoder.js +1 -1
  31. package/src/lib/orchestrator/internals/errors.js +12 -3
  32. package/src/lib/orchestrator/internals/execute-funds-only.d.ts +2 -2
  33. package/src/lib/orchestrator/internals/execute-funds-only.js +297 -259
  34. package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +2 -2
  35. package/src/lib/orchestrator/internals/execute-funds-payload.js +220 -220
  36. package/src/lib/orchestrator/internals/execute-standard.d.ts +0 -8
  37. package/src/lib/orchestrator/internals/execute-standard.js +339 -342
  38. package/src/lib/orchestrator/internals/gas-calculator.js +497 -520
  39. package/src/lib/orchestrator/internals/gas-usd-sizer.js +101 -110
  40. package/src/lib/orchestrator/internals/gateway-client.js +102 -103
  41. package/src/lib/orchestrator/internals/helpers.js +30 -27
  42. package/src/lib/orchestrator/internals/inbound-tracker.js +234 -238
  43. package/src/lib/orchestrator/internals/outbound-sync.js +463 -452
  44. package/src/lib/orchestrator/internals/outbound-tracker.js +27 -34
  45. package/src/lib/orchestrator/internals/payload-builder.d.ts +2 -2
  46. package/src/lib/orchestrator/internals/payload-builder.js +119 -100
  47. package/src/lib/orchestrator/internals/pc-usd-oracle.js +121 -129
  48. package/src/lib/orchestrator/internals/pc20/address-codec.d.ts +31 -0
  49. package/src/lib/orchestrator/internals/pc20/address-codec.js +124 -0
  50. package/src/lib/orchestrator/internals/pc20/chain-namespace.d.ts +68 -0
  51. package/src/lib/orchestrator/internals/pc20/chain-namespace.js +136 -0
  52. package/src/lib/orchestrator/internals/pc20/errors.d.ts +160 -0
  53. package/src/lib/orchestrator/internals/pc20/errors.js +236 -0
  54. package/src/lib/orchestrator/internals/pc20/export.d.ts +172 -0
  55. package/src/lib/orchestrator/internals/pc20/export.js +412 -0
  56. package/src/lib/orchestrator/internals/pc20/gate.d.ts +67 -0
  57. package/src/lib/orchestrator/internals/pc20/gate.js +184 -0
  58. package/src/lib/orchestrator/internals/pc20/resolver.d.ts +187 -0
  59. package/src/lib/orchestrator/internals/pc20/resolver.js +658 -0
  60. package/src/lib/orchestrator/internals/pc20/svm.d.ts +189 -0
  61. package/src/lib/orchestrator/internals/pc20/svm.js +286 -0
  62. package/src/lib/orchestrator/internals/pc20/tracking.d.ts +46 -0
  63. package/src/lib/orchestrator/internals/pc20/tracking.js +109 -0
  64. package/src/lib/orchestrator/internals/preflight.js +1 -1
  65. package/src/lib/orchestrator/internals/progress-route-hooks.js +2 -2
  66. package/src/lib/orchestrator/internals/push-chain-tx.js +319 -331
  67. package/src/lib/orchestrator/internals/quote.js +90 -94
  68. package/src/lib/orchestrator/internals/rescue.js +76 -79
  69. package/src/lib/orchestrator/internals/response-builder.d.ts +28 -1
  70. package/src/lib/orchestrator/internals/response-builder.js +817 -701
  71. package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -15
  72. package/src/lib/orchestrator/internals/route-handlers.js +1512 -1178
  73. package/src/lib/orchestrator/internals/signing.js +78 -83
  74. package/src/lib/orchestrator/internals/svm-bridge.d.ts +2 -2
  75. package/src/lib/orchestrator/internals/svm-bridge.js +161 -93
  76. package/src/lib/orchestrator/internals/svm-helpers.d.ts +15 -1
  77. package/src/lib/orchestrator/internals/svm-helpers.js +68 -24
  78. package/src/lib/orchestrator/internals/svm-rent.d.ts +19 -1
  79. package/src/lib/orchestrator/internals/svm-rent.js +163 -141
  80. package/src/lib/orchestrator/internals/tx-transformer.js +26 -23
  81. package/src/lib/orchestrator/internals/uea-manager.js +89 -86
  82. package/src/lib/orchestrator/max-pc-for-gas.js +5 -6
  83. package/src/lib/orchestrator/orchestrator.js +217 -200
  84. package/src/lib/orchestrator/orchestrator.types.d.ts +121 -1
  85. package/src/lib/orchestrator/orchestrator.types.js +19 -0
  86. package/src/lib/orchestrator/payload-builders.d.ts +2 -2
  87. package/src/lib/orchestrator/payload-builders.js +26 -24
  88. package/src/lib/orchestrator/route-detector.js +26 -9
  89. package/src/lib/orchestrator/svm-idl/ix-encoder.js +1 -2
  90. package/src/lib/orchestrator/svm-idl/normalize-address.js +2 -2
  91. package/src/lib/orchestrator/svm-idl/resolve.js +1 -2
  92. package/src/lib/price-fetch/price-fetch.js +67 -68
  93. package/src/lib/progress-hook/progress-hook.js +124 -72
  94. package/src/lib/push-chain/push-chain.js +171 -153
  95. package/src/lib/push-client/push-client.js +223 -237
  96. package/src/lib/universal/account/account.js +349 -365
  97. package/src/lib/universal/signer/signer.d.ts +2 -1
  98. package/src/lib/universal/signer/signer.js +315 -299
  99. package/src/lib/universal-tx-detector/auto-detect.js +55 -60
  100. package/src/lib/universal-tx-detector/cascade.js +160 -172
  101. package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
  102. package/src/lib/universal-tx-detector/classify.js +2 -3
  103. package/src/lib/universal-tx-detector/detector-svm.js +139 -147
  104. package/src/lib/universal-tx-detector/detector.js +145 -157
  105. package/src/lib/universal-tx-detector/normalize.js +7 -7
  106. package/src/lib/utils/external-tx-hash.js +1 -2
  107. package/src/lib/utils.d.ts +44 -0
  108. package/src/lib/utils.js +762 -683
  109. package/src/lib/vm-client/evm-client.js +186 -202
  110. package/src/lib/vm-client/svm-client.d.ts +1 -1
  111. package/src/lib/vm-client/svm-client.js +220 -224
  112. package/src/lib/vm-client/vm-client.types.d.ts +14 -2
@@ -0,0 +1,412 @@
1
+ "use strict";
2
+ /**
3
+ * Push-to-external PC20 export.
4
+ *
5
+ * Three things differ from the PRC20 outbound path, and all three are
6
+ * load-bearing:
7
+ *
8
+ * 1. The payload carries the `PC20` selector plus the destination wrapper's
9
+ * metadata. The SDK builds this — unlike inbound, where the external
10
+ * gateway prepends the selector.
11
+ * 2. The gas quote comes from `getPC20ExportGasAndFees`, not
12
+ * `getOutboundTxGasAndFees`. Only the former knows about the first-export
13
+ * deployment overhead.
14
+ * 3. The source token IS approved to UGPC. This is the mirror image of
15
+ * inbound: an export locks the Push-native token into VaultPC20, so the
16
+ * gateway must be able to pull it.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.MAX_PC20_SYMBOL_BYTES = exports.MAX_PC20_NAME_BYTES = exports.PC20_SELECTOR = void 0;
43
+ exports.buildPC20ExportPayload = buildPC20ExportPayload;
44
+ exports.assertMetadataFitsFactoryLimits = assertMetadataFitsFactoryLimits;
45
+ exports.quotePC20Export = quotePC20Export;
46
+ exports.queryPC20OutboundGasFee = queryPC20OutboundGasFee;
47
+ exports.resolveDestinationWrapper = resolveDestinationWrapper;
48
+ exports.validatePC20Export = validatePC20Export;
49
+ const viem_1 = require("viem");
50
+ const abi_1 = require("../../../constants/abi");
51
+ const chain_1 = require("../../../constants/chain");
52
+ const context_1 = require("../context");
53
+ const chain_namespace_1 = require("./chain-namespace");
54
+ const resolver_1 = require("./resolver");
55
+ const address_codec_1 = require("./address-codec");
56
+ const errors_1 = require("./errors");
57
+ /** `bytes4(ascii("PC20"))`. Must match the chain's PC20 route selector. */
58
+ exports.PC20_SELECTOR = '0x50433230';
59
+ /**
60
+ * Factory limits on the metadata forwarded to the destination.
61
+ *
62
+ * Mirrors `PC20Factory.MAX_NAME_LENGTH` / `MAX_SYMBOL_LENGTH`. Pinned by test
63
+ * against the reference contract revision — exceeding them reverts on the
64
+ * destination chain, long after the source token has been locked, so the SDK
65
+ * fails fast on the Push side instead.
66
+ */
67
+ exports.MAX_PC20_NAME_BYTES = 64;
68
+ exports.MAX_PC20_SYMBOL_BYTES = 32;
69
+ /**
70
+ * Build the outbound payload:
71
+ *
72
+ * PC20_SELECTOR
73
+ * || abi.encode(destChainNamespace, name, symbol, decimals)
74
+ * || rawDestinationUserData
75
+ *
76
+ * The user data is appended raw, not ABI-encoded into the tuple — the
77
+ * destination settlement path strips the fixed-width prefix and forwards the
78
+ * remainder verbatim.
79
+ */
80
+ function buildPC20ExportPayload(params) {
81
+ const namespace = (0, chain_namespace_1.chainToNamespace)(params.destinationChain);
82
+ assertMetadataFitsFactoryLimits(params.name, params.symbol);
83
+ const metadata = (0, viem_1.encodeAbiParameters)([
84
+ { name: 'destChainNamespace', type: 'string' },
85
+ { name: 'name', type: 'string' },
86
+ { name: 'symbol', type: 'string' },
87
+ { name: 'decimals', type: 'uint8' },
88
+ ], [namespace, params.name, params.symbol, params.decimals]);
89
+ const userData = params.destinationUserData ?? '0x';
90
+ return (0, viem_1.encodePacked)(['bytes4', 'bytes', 'bytes'], [exports.PC20_SELECTOR, metadata, userData]);
91
+ }
92
+ /**
93
+ * Reject metadata the destination factory would refuse.
94
+ *
95
+ * Byte length, not character count — a multi-byte symbol can pass a `.length`
96
+ * check and still revert on chain.
97
+ */
98
+ function assertMetadataFitsFactoryLimits(name, symbol) {
99
+ const nameBytes = new TextEncoder().encode(name).length;
100
+ const symbolBytes = new TextEncoder().encode(symbol).length;
101
+ if (nameBytes === 0 || symbolBytes === 0) {
102
+ throw new errors_1.InvalidPC20MetadataError('PC20 name and symbol must be non-empty.');
103
+ }
104
+ if (nameBytes > exports.MAX_PC20_NAME_BYTES) {
105
+ throw new errors_1.InvalidPC20MetadataError(`PC20 name is ${nameBytes} bytes; the destination factory allows ${exports.MAX_PC20_NAME_BYTES}.`);
106
+ }
107
+ if (symbolBytes > exports.MAX_PC20_SYMBOL_BYTES) {
108
+ throw new errors_1.InvalidPC20MetadataError(`PC20 symbol is ${symbolBytes} bytes; the destination factory allows ${exports.MAX_PC20_SYMBOL_BYTES}.`);
109
+ }
110
+ }
111
+ /**
112
+ * Quote a PC20 export.
113
+ *
114
+ * ## Why the deployment margin is unconditional
115
+ *
116
+ * `isFirstExport` is read before the transaction is included, so it is a
117
+ * time-of-check/time-of-use value. Two things can happen in between:
118
+ *
119
+ * - quoted `true`, another export deploys the wrapper first → the quote
120
+ * over-funds. Harmless.
121
+ * - quoted `false`, the wrapper turns out to be absent at execution (registry
122
+ * rollback, factory reconfiguration, reorg) → the quote under-funds by
123
+ * `pc20DeploymentGasOverhead` and the transfer strands on the destination
124
+ * with the source token already locked.
125
+ *
126
+ * The second is unrecoverable without operator intervention, so the ceiling
127
+ * always covers a deployment. `maxPCForGas` is a ceiling rather than a charge —
128
+ * the unspent remainder is not consumed — so the only cost is a higher required
129
+ * balance. `gasLimitUsed` itself is passed through unchanged.
130
+ */
131
+ async function quotePC20Export(ctx, pushPC20, destinationChain, gasLimit, opts) {
132
+ const namespace = (0, chain_namespace_1.chainToNamespace)(destinationChain);
133
+ const core = await (0, resolver_1.getUniversalCoreAddress)(opts);
134
+ const result = await ctx.pushClient.readContract({
135
+ address: core,
136
+ abi: abi_1.UNIVERSAL_CORE_EVM,
137
+ functionName: 'getPC20ExportGasAndFees',
138
+ args: [namespace, gasLimit, pushPC20],
139
+ });
140
+ const [gasToken, gasFee, protocolFee, gasPrice, chainNamespace, gasLimitUsed, isFirstExport] = result;
141
+ const overhead = await ctx.pushClient.readContract({
142
+ address: core,
143
+ abi: abi_1.UNIVERSAL_CORE_EVM,
144
+ functionName: 'pc20DeploymentGasOverhead',
145
+ args: [namespace],
146
+ });
147
+ // Add the overhead even when the quote already included it. Double-counting
148
+ // raises a ceiling that is never fully spent; under-counting strands funds.
149
+ const gasLimitCeiling = gasLimitUsed + overhead;
150
+ (0, context_1.printLog)(ctx, `quotePC20Export — namespace=${chainNamespace}, gasLimitUsed=${gasLimitUsed}, ` +
151
+ `isFirstExport=${isFirstExport}, overhead=${overhead}, ceiling=${gasLimitCeiling}`);
152
+ return {
153
+ gasToken,
154
+ gasFee,
155
+ protocolFee,
156
+ gasPrice,
157
+ chainNamespace,
158
+ gasLimitUsed,
159
+ isFirstExport,
160
+ gasLimitCeiling,
161
+ };
162
+ }
163
+ /**
164
+ * PC20 quote adapted to the shape `queryOutboundGasFee` returns, so the shared
165
+ * R2 gas-sizing code downstream is untouched.
166
+ *
167
+ * `nativeValueForGas` and `sizing` are intentionally absent — the caller's
168
+ * native-PC swap sizing consumes `gasFee`/`protocolFee`/`gasPrice` the same way
169
+ * it does for PRC20. What differs is only where the numbers came from.
170
+ */
171
+ async function queryPC20OutboundGasFee(ctx, pushPC20, gasLimit, destinationChain) {
172
+ const opts = {
173
+ network: ctx.pushNetwork,
174
+ rpcUrls: ctx.rpcUrls,
175
+ };
176
+ const quote = await quotePC20Export(ctx, pushPC20, destinationChain, gasLimit, opts);
177
+ const universalCoreAddress = await (0, resolver_1.getUniversalCoreAddress)(opts);
178
+ return {
179
+ gasToken: quote.gasToken,
180
+ gasFee: quote.gasFee,
181
+ protocolFee: quote.protocolFee,
182
+ gasPrice: quote.gasPrice,
183
+ gasLimitUsed: quote.gasLimitUsed,
184
+ universalCoreAddress,
185
+ isFirstExport: quote.isFirstExport,
186
+ gasLimitCeiling: quote.gasLimitCeiling,
187
+ nativeValueForGas: BigInt(0),
188
+ };
189
+ }
190
+ /**
191
+ * Resolve the wrapper address the destination transfer should target.
192
+ *
193
+ * For a repeat export this is the registered wrapper. For a first export the
194
+ * wrapper does not exist yet, but the destination settlement mints into the CEA
195
+ * and then executes user data — so the SDK still needs the address in order to
196
+ * build the transfer that delivers funds to the actual recipient.
197
+ *
198
+ * ## Funds-at-risk ordering
199
+ *
200
+ * This must run BEFORE the source-token approval. By the time the outbound is
201
+ * submitted the token is locked in VaultPC20; a wrapper address that turns out
202
+ * to be wrong then means a transfer to a dead address with no way back except
203
+ * the revert path. Every failure here therefore throws rather than falling back
204
+ * to a guess.
205
+ *
206
+ * The prediction is read from the live factory's own `computeWrapperAddress`,
207
+ * which derives from its own `type(PC20Wrapper).creationCode`. That is why the
208
+ * SDK does not mirror the CREATE2 derivation locally: a mirrored init-code hash
209
+ * can drift from the deployed bytecode, while asking the factory cannot.
210
+ *
211
+ * ## Which factory to ask
212
+ *
213
+ * Two contracts hold a `pc20Factory` pointer, and they are not the same slot:
214
+ *
215
+ * - `UniversalCore.pc20FactoryByChain(ns)` on Push — the protocol's registry
216
+ * mirror, and the value Tier B cross-checks against.
217
+ * - `Vault.pc20Factory()` on the destination chain — the contract that
218
+ * actually calls `deployWrapper` at settlement (`Vault.sol:327`).
219
+ *
220
+ * The Vault is therefore the authoritative answer to "where will the wrapper
221
+ * land"; the registry copy can go stale without settlement noticing. The
222
+ * deployed UniversalCore does not expose `pc20FactoryByChain` — by chain-team
223
+ * decision the token mappings plus the Vault's own pointer are the supported
224
+ * surface — so the Vault read is the standing path. The registry attempt is
225
+ * kept first purely for forward compatibility: if a future upgrade adds the
226
+ * accessor, the two become cross-checked with no SDK change.
227
+ */
228
+ async function resolveDestinationWrapper(ctx, pushPC20, destinationChain, opts) {
229
+ const namespace = (0, chain_namespace_1.chainToNamespace)(destinationChain);
230
+ const registered = await (0, resolver_1.getRegisteredWrapper)(pushPC20, destinationChain, opts);
231
+ if (registered) {
232
+ return { address: registered.address, deployed: true };
233
+ }
234
+ if ((0, chain_namespace_1.vmForChain)(destinationChain) !== 'EVM') {
235
+ // SVM: the wrapper is the deterministic `pc20_mint` PDA — a pure local
236
+ // derivation, so unlike EVM there is no factory to consult and prediction
237
+ // cannot drift from deployment (the program derives from the same seeds,
238
+ // state.rs:13). `deployed` is decided by whether the mint account exists.
239
+ return resolveSvmDestinationWrapper(pushPC20, destinationChain, opts);
240
+ }
241
+ // First export — predict from the live factory.
242
+ const { factory, source } = await resolveDestinationFactory(ctx, destinationChain, opts);
243
+ (0, context_1.printLog)(ctx, `resolveDestinationWrapper — factory ${factory} (via ${source})`);
244
+ let predicted;
245
+ try {
246
+ predicted = await readPredictedWrapper(ctx, factory, pushPC20, destinationChain, opts);
247
+ }
248
+ catch (err) {
249
+ throw new errors_1.PC20WrapperPredictionUnavailableError({
250
+ chain: String(destinationChain),
251
+ address: factory,
252
+ chainNamespace: namespace,
253
+ hint: 'The destination factory could not be reached to compute the wrapper ' +
254
+ 'address. Aborted before approval, so no funds are locked. ' +
255
+ `(${err instanceof Error ? err.message : String(err)})`,
256
+ });
257
+ }
258
+ return { address: (0, viem_1.getAddress)(predicted), deployed: false };
259
+ }
260
+ /**
261
+ * SVM destination wrapper: the deterministic `pc20_mint` PDA.
262
+ *
263
+ * Derived locally from `[b"pc20_mint", source_asset_20]` under the gateway
264
+ * program (mirrors `pc20.rs` / validated against the deployed devnet binary,
265
+ * which contains the same seed strings). Existence of the mint account is what
266
+ * distinguishes a repeat export from a first one — settlement creates the mint
267
+ * idempotently either way, so a wrong `deployed` flag here costs gas headroom,
268
+ * not funds.
269
+ */
270
+ async function resolveSvmDestinationWrapper(pushPC20, destinationChain, opts) {
271
+ const { PublicKey, Connection } = await Promise.resolve().then(() => __importStar(require('@solana/web3.js')));
272
+ const { SVM_GATEWAY_IDL } = await Promise.resolve().then(() => __importStar(require('../../../constants/abi')));
273
+ const { predictSvmWrapperMint } = await Promise.resolve().then(() => __importStar(require('./svm')));
274
+ const programId = new PublicKey(SVM_GATEWAY_IDL.address);
275
+ const mint = predictSvmWrapperMint(programId, pushPC20);
276
+ const rpc = opts.rpcUrls?.[destinationChain]?.[0] ??
277
+ chain_1.CHAIN_INFO[destinationChain]?.defaultRPC?.[0];
278
+ if (!rpc) {
279
+ throw new errors_1.UnsupportedPC20DestinationError({
280
+ chain: String(destinationChain),
281
+ hint: 'No RPC URL configured for this chain.',
282
+ });
283
+ }
284
+ let deployed = false;
285
+ try {
286
+ const info = await new Connection(rpc).getAccountInfo(mint);
287
+ deployed = info !== null;
288
+ }
289
+ catch (err) {
290
+ // The PDA derivation is still valid; only the deployed flag is unknown.
291
+ // Downstream decisions (gas headroom, whether recipient-ATA delivery is
292
+ // even possible) hinge on that flag, so guessing is worse than aborting —
293
+ // and this runs pre-approval, so aborting locks nothing.
294
+ throw new errors_1.PC20WrapperPredictionUnavailableError({
295
+ chain: String(destinationChain),
296
+ address: mint.toBase58(),
297
+ hint: 'Could not query the Solana RPC to check mint existence. Aborted ' +
298
+ `before approval, so no funds are locked. (${err instanceof Error ? err.message : String(err)})`,
299
+ });
300
+ }
301
+ return { address: mint.toBase58(), deployed };
302
+ }
303
+ /**
304
+ * Find the factory that will deploy the destination wrapper.
305
+ *
306
+ * Prefers UniversalCore's registry so the value stays cross-checked against the
307
+ * protocol's own view. Falls back to the destination `Vault.pc20Factory()` —
308
+ * the contract that actually calls `deployWrapper` — when the registry has no
309
+ * entry or does not expose the accessor at all.
310
+ *
311
+ * The fallback is not a guess. The Vault is strictly closer to the truth for
312
+ * this question: settlement reads *its* pointer, so a wrapper predicted from it
313
+ * cannot disagree with where the wrapper is deployed, whereas a stale registry
314
+ * mirror can.
315
+ */
316
+ async function resolveDestinationFactory(ctx, destinationChain, opts) {
317
+ const namespace = (0, chain_namespace_1.chainToNamespace)(destinationChain);
318
+ const core = await (0, resolver_1.getUniversalCoreAddress)(opts);
319
+ const factoryRaw = await ctx.pushClient
320
+ .readContract({
321
+ address: core,
322
+ abi: abi_1.UNIVERSAL_CORE_EVM,
323
+ functionName: 'pc20FactoryByChain',
324
+ args: [namespace],
325
+ })
326
+ .catch(() => undefined);
327
+ if (factoryRaw && !(0, address_codec_1.isZeroBytes32)(factoryRaw)) {
328
+ return {
329
+ factory: (0, viem_1.getAddress)((0, address_codec_1.pc20Bytes32ToAddress)(destinationChain, factoryRaw)),
330
+ source: 'registry',
331
+ };
332
+ }
333
+ const vault = chain_1.VAULT_ADDRESSES[destinationChain];
334
+ if (!vault) {
335
+ throw new errors_1.UnsupportedPC20DestinationError({
336
+ chain: String(destinationChain),
337
+ chainNamespace: namespace,
338
+ hint: 'UniversalCore has no PC20 factory registered for this chain, and no ' +
339
+ 'Vault address is configured to fall back to.',
340
+ });
341
+ }
342
+ const client = destinationClient(destinationChain, opts);
343
+ const vaultFactory = (await client
344
+ .readContract({
345
+ address: (0, viem_1.getAddress)(vault),
346
+ abi: abi_1.VAULT_PC20_FACTORY_EVM,
347
+ functionName: 'pc20Factory',
348
+ args: [],
349
+ })
350
+ .catch(() => undefined));
351
+ if (!vaultFactory || vaultFactory === '0x0000000000000000000000000000000000000000') {
352
+ throw new errors_1.UnsupportedPC20DestinationError({
353
+ chain: String(destinationChain),
354
+ chainNamespace: namespace,
355
+ hint: 'Neither UniversalCore nor the destination Vault has a PC20 factory set.',
356
+ });
357
+ }
358
+ return { factory: (0, viem_1.getAddress)(vaultFactory), source: 'vault' };
359
+ }
360
+ /** Public client for a destination chain, honouring `rpcUrls` overrides. */
361
+ function destinationClient(chain, opts) {
362
+ const rpc = opts.rpcUrls?.[chain]?.[0] ?? chain_1.CHAIN_INFO[chain]?.defaultRPC?.[0];
363
+ if (!rpc) {
364
+ throw new errors_1.UnsupportedPC20DestinationError({
365
+ chain: String(chain),
366
+ hint: 'No RPC URL configured for this chain.',
367
+ });
368
+ }
369
+ return (0, viem_1.createPublicClient)({ transport: (0, viem_1.http)(rpc) });
370
+ }
371
+ async function readPredictedWrapper(ctx, factory, pushPC20, destinationChain, opts) {
372
+ const { createPublicClient, http } = await Promise.resolve().then(() => __importStar(require('viem')));
373
+ const rpc = opts.rpcUrls?.[destinationChain]?.[0] ??
374
+ (await Promise.resolve().then(() => __importStar(require('../../../constants/chain')))).CHAIN_INFO[destinationChain]
375
+ ?.defaultRPC?.[0];
376
+ if (!rpc) {
377
+ throw new Error(`No RPC configured for ${destinationChain}`);
378
+ }
379
+ const client = createPublicClient({ transport: http(rpc) });
380
+ const predicted = (await client.readContract({
381
+ address: factory,
382
+ abi: abi_1.PC20_FACTORY_EVM,
383
+ functionName: 'computeWrapperAddress',
384
+ args: [pushPC20],
385
+ }));
386
+ if (!predicted || predicted === '0x0000000000000000000000000000000000000000') {
387
+ throw new Error('factory returned the zero address');
388
+ }
389
+ (0, context_1.printLog)(ctx, `resolveDestinationWrapper — predicted wrapper ${predicted} via factory ${factory}`);
390
+ return predicted;
391
+ }
392
+ // ---------------------------------------------------------------------------
393
+ // Validation entry point
394
+ // ---------------------------------------------------------------------------
395
+ /**
396
+ * Everything that must hold before a PC20 export approves or broadcasts.
397
+ *
398
+ * Ordered so the cheapest, most likely failures come first and nothing that
399
+ * touches funds runs until all of them pass.
400
+ */
401
+ async function validatePC20Export(ctx, resolved, destinationChain, opts) {
402
+ const namespace = (0, chain_namespace_1.chainToNamespace)(destinationChain);
403
+ assertMetadataFitsFactoryLimits(resolved.name, resolved.symbol);
404
+ // The destination factory is deliberately NOT read here. A repeat export
405
+ // targets an already-registered wrapper and never needs it, so requiring it
406
+ // up front would fail exports that are perfectly safe. Only the first-export
407
+ // prediction path depends on the factory, and `resolveDestinationWrapper`
408
+ // reads it there — where a failure correctly aborts before approval.
409
+ const wrapper = await resolveDestinationWrapper(ctx, resolved.pushAddress, destinationChain, opts);
410
+ return { wrapper, namespace };
411
+ }
412
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The PC20 gate.
3
+ *
4
+ * `funds.token` publicly accepts a `MoveableToken` or a `PC20TokenReference`.
5
+ * Exactly one place decides which branch a transaction takes, and it is here.
6
+ * Everything downstream types against `LegacyExecuteParams`, whose `funds.token`
7
+ * is an `InternalFundsToken` and whose `_pc20` descriptor is authoritative for
8
+ * PC20 behavior. A PC20 reference reaching a builder unresolved is a compile
9
+ * error, not a runtime `undefined`.
10
+ *
11
+ * Resolution happens once, here, before approvals, fee calculation, or any
12
+ * broadcast — so a chain/address mismatch costs the caller nothing.
13
+ */
14
+ import type { ExecuteParams, LegacyExecuteParams } from '../../orchestrator.types';
15
+ import type { OrchestratorContext } from '../context';
16
+ import { type ResolvedPC20 } from './resolver';
17
+ type PC20AwareParams = {
18
+ funds?: {
19
+ amount: bigint;
20
+ };
21
+ _pc20?: ResolvedPC20;
22
+ };
23
+ /** True when this transaction's funds are a PC20 reference. */
24
+ export declare function isPC20Transaction(params: ExecuteParams): boolean;
25
+ /** True only for a resolved, funds-bearing external-wrapper burn. */
26
+ export declare function isPC20ImportWithFunds(params: PC20AwareParams): boolean;
27
+ /**
28
+ * Fail closed before a wrapper burn can be broadcast without a real user
29
+ * payload behind the PC20 selector.
30
+ */
31
+ export declare function assertPC20ImportHasPayload(params: PC20AwareParams, hasPayload: boolean, path: string): void;
32
+ /**
33
+ * Validate chain ownership and resolve the PC20 against the live registry.
34
+ *
35
+ * Returns params shaped for the shared execution path, with `_pc20` carrying
36
+ * the resolved descriptor. The caller's token object is never mutated.
37
+ */
38
+ export declare function resolvePC20Funds(ctx: OrchestratorContext, params: ExecuteParams): Promise<LegacyExecuteParams>;
39
+ /**
40
+ * Narrow `ExecuteParams` to the shared execution shape.
41
+ *
42
+ * Synchronous counterpart used where no PC20 resolution is possible or needed.
43
+ * Throws if a PC20 reference reaches it, which would mean the async gate was
44
+ * skipped.
45
+ */
46
+ export declare function assertLegacyFunds(params: ExecuteParams): LegacyExecuteParams;
47
+ /**
48
+ * Re-verify a prepared PC20 transaction at send time.
49
+ *
50
+ * A prepared tx is built now and broadcast later. Between the two, the registry
51
+ * can change in a way that matters:
52
+ *
53
+ * - a first export lands and creates a wrapper mapping that did not exist,
54
+ * turning a predicted destination address into a registered one;
55
+ * - a factory is reconfigured, invalidating the identity the payload assumed.
56
+ *
57
+ * Only the critical reads are repeated — forward/reverse registry agreement and
58
+ * the Push source binding. Re-running the whole Tier B sweep on every send of a
59
+ * prepared tx would cost more than it protects.
60
+ *
61
+ * Throws if the resolved Push source has moved. That is not recoverable by
62
+ * retrying: the signed payload already encodes the old source.
63
+ */
64
+ export declare function revalidatePreparedPC20(ctx: OrchestratorContext, prepared: LegacyExecuteParams): Promise<void>;
65
+ /** Resolve PC20 funds when present; otherwise pass the legacy shape through. */
66
+ export declare function gateFunds(ctx: OrchestratorContext, params: ExecuteParams): Promise<LegacyExecuteParams>;
67
+ export {};
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * The PC20 gate.
4
+ *
5
+ * `funds.token` publicly accepts a `MoveableToken` or a `PC20TokenReference`.
6
+ * Exactly one place decides which branch a transaction takes, and it is here.
7
+ * Everything downstream types against `LegacyExecuteParams`, whose `funds.token`
8
+ * is an `InternalFundsToken` and whose `_pc20` descriptor is authoritative for
9
+ * PC20 behavior. A PC20 reference reaching a builder unresolved is a compile
10
+ * error, not a runtime `undefined`.
11
+ *
12
+ * Resolution happens once, here, before approvals, fee calculation, or any
13
+ * broadcast — so a chain/address mismatch costs the caller nothing.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.isPC20Transaction = isPC20Transaction;
17
+ exports.isPC20ImportWithFunds = isPC20ImportWithFunds;
18
+ exports.assertPC20ImportHasPayload = assertPC20ImportHasPayload;
19
+ exports.resolvePC20Funds = resolvePC20Funds;
20
+ exports.assertLegacyFunds = assertLegacyFunds;
21
+ exports.revalidatePreparedPC20 = revalidatePreparedPC20;
22
+ exports.gateFunds = gateFunds;
23
+ const orchestrator_types_1 = require("../../orchestrator.types");
24
+ const helpers_1 = require("../helpers");
25
+ const resolver_1 = require("./resolver");
26
+ const chain_namespace_1 = require("./chain-namespace");
27
+ const errors_1 = require("./errors");
28
+ /** True when this transaction's funds are a PC20 reference. */
29
+ function isPC20Transaction(params) {
30
+ return (0, orchestrator_types_1.isPC20Reference)(params.funds?.token);
31
+ }
32
+ /** True only for a resolved, funds-bearing external-wrapper burn. */
33
+ function isPC20ImportWithFunds(params) {
34
+ return (params._pc20?.direction === 'import' &&
35
+ (params.funds?.amount ?? BigInt(0)) > BigInt(0));
36
+ }
37
+ /**
38
+ * Fail closed before a wrapper burn can be broadcast without a real user
39
+ * payload behind the PC20 selector.
40
+ */
41
+ function assertPC20ImportHasPayload(params, hasPayload, path) {
42
+ if (!isPC20ImportWithFunds(params) || hasPayload)
43
+ return;
44
+ const descriptor = params._pc20;
45
+ if (!descriptor)
46
+ return;
47
+ throw new errors_1.PC20UnsafeEmptyPayloadError({
48
+ chain: String(descriptor.originChain),
49
+ address: descriptor.originAddress,
50
+ chainNamespace: descriptor.chainNamespace,
51
+ hint: `${path} produced an empty return payload. ` +
52
+ 'No transaction was submitted; prepare it again with a fixed SDK.',
53
+ });
54
+ }
55
+ /**
56
+ * Where the funds token must live for this transaction.
57
+ *
58
+ * `to.chain` is the *destination* and is never the answer — mistaking the two
59
+ * is the single most likely caller error, which is why it gets its own check
60
+ * and its own error rather than failing later as "wrapper not registered".
61
+ */
62
+ function expectedFundsChain(ctx, params) {
63
+ // Explicit CEA-origin route (Routes 3/4): the funds sit on the `from` chain.
64
+ const fromChain = params.from?.chain;
65
+ if (fromChain)
66
+ return fromChain;
67
+ // Push-to-external export: the caller named a Push-native token.
68
+ const token = params.funds?.token;
69
+ if (token && (0, chain_namespace_1.isPushChain)(token.chain)) {
70
+ return (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
71
+ }
72
+ // Direct external-to-Push: the funds are wherever the signer is.
73
+ return ctx.universalSigner.account.chain;
74
+ }
75
+ /**
76
+ * Validate chain ownership and resolve the PC20 against the live registry.
77
+ *
78
+ * Returns params shaped for the shared execution path, with `_pc20` carrying
79
+ * the resolved descriptor. The caller's token object is never mutated.
80
+ */
81
+ async function resolvePC20Funds(ctx, params) {
82
+ const token = params.funds?.token;
83
+ const amount = params.funds?.amount ?? BigInt(0);
84
+ if (amount <= BigInt(0)) {
85
+ throw new Error('funds.amount must be greater than zero for a PC20 transfer.');
86
+ }
87
+ const expected = expectedFundsChain(ctx, params);
88
+ if (token.chain !== expected) {
89
+ throw new errors_1.PC20TokenChainMismatchError({
90
+ chain: String(token.chain),
91
+ address: token.address,
92
+ expectedChain: String(expected),
93
+ });
94
+ }
95
+ const resolved = await (0, resolver_1.resolvePC20Token)(token.chain, token.address, {
96
+ network: ctx.pushNetwork,
97
+ rpcUrls: ctx.rpcUrls,
98
+ // Factory identity is Tier B. Run it on first use of a given
99
+ // (chain, address) per session; the resolver caches the verdict.
100
+ tierB: true,
101
+ });
102
+ return {
103
+ ...params,
104
+ funds: {
105
+ amount,
106
+ token: {
107
+ symbol: resolved.symbol,
108
+ decimals: resolved.decimals,
109
+ // The gateway request carries the *external wrapper* address. The
110
+ // Push-side transfer uses `_pc20.pushAddress` instead.
111
+ address: resolved.originAddress,
112
+ mechanism: 'pc20-burn',
113
+ },
114
+ },
115
+ _pc20: resolved,
116
+ };
117
+ }
118
+ /**
119
+ * Narrow `ExecuteParams` to the shared execution shape.
120
+ *
121
+ * Synchronous counterpart used where no PC20 resolution is possible or needed.
122
+ * Throws if a PC20 reference reaches it, which would mean the async gate was
123
+ * skipped.
124
+ */
125
+ function assertLegacyFunds(params) {
126
+ if ((0, orchestrator_types_1.isPC20Reference)(params.funds?.token)) {
127
+ throw new Error('Internal: a PC20 funds reference reached the legacy path without ' +
128
+ 'resolution. resolvePC20Funds() must run first.');
129
+ }
130
+ return params;
131
+ }
132
+ /**
133
+ * Re-verify a prepared PC20 transaction at send time.
134
+ *
135
+ * A prepared tx is built now and broadcast later. Between the two, the registry
136
+ * can change in a way that matters:
137
+ *
138
+ * - a first export lands and creates a wrapper mapping that did not exist,
139
+ * turning a predicted destination address into a registered one;
140
+ * - a factory is reconfigured, invalidating the identity the payload assumed.
141
+ *
142
+ * Only the critical reads are repeated — forward/reverse registry agreement and
143
+ * the Push source binding. Re-running the whole Tier B sweep on every send of a
144
+ * prepared tx would cost more than it protects.
145
+ *
146
+ * Throws if the resolved Push source has moved. That is not recoverable by
147
+ * retrying: the signed payload already encodes the old source.
148
+ */
149
+ async function revalidatePreparedPC20(ctx, prepared) {
150
+ const descriptor = prepared._pc20;
151
+ if (!descriptor)
152
+ return;
153
+ // An export's identity is the Push token itself, which the payload pins; a
154
+ // registry change cannot redirect it. Only imports need the recheck.
155
+ if (descriptor.direction !== 'import')
156
+ return;
157
+ const fresh = await (0, resolver_1.resolvePC20Token)(descriptor.originChain, descriptor.originAddress, {
158
+ network: ctx.pushNetwork,
159
+ rpcUrls: ctx.rpcUrls,
160
+ // Tier A only. Tier B already passed at prepare time and its subjects
161
+ // (factory identity, deployed bytecode) do not change without a redeploy.
162
+ tierB: false,
163
+ // Prepared transactions must compare against live registry state rather
164
+ // than the positive result cached while they were constructed.
165
+ bypassCache: true,
166
+ });
167
+ if (fresh.pushAddress.toLowerCase() !== descriptor.pushAddress.toLowerCase()) {
168
+ throw new errors_1.PC20RegistryMismatchError({
169
+ chain: String(descriptor.originChain),
170
+ address: descriptor.originAddress,
171
+ chainNamespace: descriptor.chainNamespace,
172
+ resolvedWrapper: fresh.pushAddress,
173
+ hint: 'The registry changed after this transaction was prepared. Prepare it ' +
174
+ 'again — the signed payload encodes the previous Push source.',
175
+ });
176
+ }
177
+ }
178
+ /** Resolve PC20 funds when present; otherwise pass the legacy shape through. */
179
+ async function gateFunds(ctx, params) {
180
+ return isPC20Transaction(params)
181
+ ? resolvePC20Funds(ctx, params)
182
+ : assertLegacyFunds(params);
183
+ }
184
+ //# sourceMappingURL=gate.js.map