@pushchain/core 6.0.20 → 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 (111) hide show
  1. package/CHANGELOG.md +48 -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 +3 -0
  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 -329
  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.js +306 -294
  98. package/src/lib/universal-tx-detector/auto-detect.js +55 -60
  99. package/src/lib/universal-tx-detector/cascade.js +160 -172
  100. package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
  101. package/src/lib/universal-tx-detector/classify.js +2 -3
  102. package/src/lib/universal-tx-detector/detector-svm.js +139 -147
  103. package/src/lib/universal-tx-detector/detector.js +145 -157
  104. package/src/lib/universal-tx-detector/normalize.js +7 -7
  105. package/src/lib/utils/external-tx-hash.js +1 -2
  106. package/src/lib/utils.d.ts +44 -0
  107. package/src/lib/utils.js +762 -683
  108. package/src/lib/vm-client/evm-client.js +186 -202
  109. package/src/lib/vm-client/svm-client.d.ts +1 -1
  110. package/src/lib/vm-client/svm-client.js +220 -224
  111. package/src/lib/vm-client/vm-client.types.d.ts +14 -2
@@ -0,0 +1,658 @@
1
+ "use strict";
2
+ /**
3
+ * PC20 registry resolver — the one module that reads PC20 state.
4
+ *
5
+ * Used by `sendTransaction`, `prepareTransaction`, cascade construction,
6
+ * `getPC20Address`, and the outbound tracking fallback.
7
+ * Nothing else may read the registry directly; a second reader is how the
8
+ * validation tiers and the cache policy drift apart.
9
+ *
10
+ * ## Read budget
11
+ *
12
+ * Validation is tiered so a send does not pay for the full identity sweep:
13
+ *
14
+ * Tier A (every send) — `getPC20Source` then `getPC20Wrapper`. Forward and
15
+ * reverse agreement proves the address is registered
16
+ * for that exact namespace, which is what defeats a
17
+ * wrapper address copied onto the wrong chain.
18
+ * Tier B (prepare-time, first use per session, or strict mode) — live gateway
19
+ * factory identity, `isPC20Wrapper`, `wrapperToSource`,
20
+ * `SOURCE_ASSET`, deployed bytecode. Largely redundant
21
+ * once Tier A holds, but it is what catches a
22
+ * misconfigured deployment where the gateway would not
23
+ * take the PC20 burn path at all.
24
+ *
25
+ * Each tier is one multicall against one chain. Budget: at most 2 sequential
26
+ * round trips warm, 4 cold. `__readCount` exists so tests can assert that.
27
+ *
28
+ * ## Cache policy
29
+ *
30
+ * Positive results are cached for the client's lifetime, keyed by
31
+ * `(pushNetwork, chain, address)`. Registered mappings are immutable in
32
+ * practice — a factory reconfiguration is a redeploy-level event.
33
+ *
34
+ * Negative results are NEVER cached. The first successful export creates a
35
+ * mapping that did not previously exist, and a cached "not registered" would
36
+ * make the SDK permanently wrong about a token that now works.
37
+ */
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.__readCount = void 0;
40
+ exports.getUniversalCoreAddress = getUniversalCoreAddress;
41
+ exports.__clearPC20Caches = __clearPC20Caches;
42
+ exports.resolveWrapperToSource = resolveWrapperToSource;
43
+ exports.verifyEvmWrapperIdentity = verifyEvmWrapperIdentity;
44
+ exports.readPushPC20Metadata = readPushPC20Metadata;
45
+ exports.listPC20Deployments = listPC20Deployments;
46
+ exports.getRegisteredWrapper = getRegisteredWrapper;
47
+ exports.discoverPC20Chain = discoverPC20Chain;
48
+ exports.resolvePC20Token = resolvePC20Token;
49
+ const viem_1 = require("viem");
50
+ const enums_1 = require("../../../constants/enums");
51
+ const chain_1 = require("../../../constants/chain");
52
+ const abi_1 = require("../../../constants/abi");
53
+ const helpers_1 = require("../helpers");
54
+ const chain_namespace_1 = require("./chain-namespace");
55
+ const address_codec_1 = require("./address-codec");
56
+ const errors_1 = require("./errors");
57
+ // ---------------------------------------------------------------------------
58
+ // Client plumbing
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * Instrumentation for the round-trip budget assertion. Incremented once per
62
+ * network round trip (a batch counts as one, which is the whole point).
63
+ */
64
+ exports.__readCount = { n: 0 };
65
+ const clientCache = new Map();
66
+ /**
67
+ * Why JSON-RPC batching rather than Multicall3.
68
+ *
69
+ * Every PC20 registry read targets UniversalCore on Push Chain, so batching is
70
+ * what keeps `getPC20Address` at one round trip instead of one per chain. The
71
+ * obvious tool is `client.multicall()` — but **Multicall3 is not deployed on
72
+ * Push Chain**, so that path fails outright with "multicallAddress is
73
+ * required". Verified against Donut: no code at
74
+ * `0xcA11bde05977b3631167028862bE2a173976CA11`.
75
+ *
76
+ * viem's transport-level `batch` option achieves the same thing a layer lower:
77
+ * concurrent `eth_call`s issued in the same tick are coalesced into a single
78
+ * JSON-RPC batch request over one HTTP round trip. No contract required, and it
79
+ * works identically on chains that do have Multicall3.
80
+ */
81
+ const BATCH_TRANSPORT_OPTS = { batch: { wait: 0 } };
82
+ function rpcFor(chain, opts) {
83
+ const override = opts.rpcUrls?.[chain]?.[0];
84
+ const fallback = chain_1.CHAIN_INFO[chain]?.defaultRPC?.[0];
85
+ const url = override ?? fallback;
86
+ if (!url) {
87
+ throw new errors_1.UnsupportedPC20DestinationError({
88
+ chain: String(chain),
89
+ hint: 'No RPC URL configured for this chain. Pass one via `rpcUrls`.',
90
+ });
91
+ }
92
+ return url;
93
+ }
94
+ function clientFor(chain, opts) {
95
+ const url = rpcFor(chain, opts);
96
+ const key = `${chain}:${url}`;
97
+ let client = clientCache.get(key);
98
+ if (!client) {
99
+ client = (0, viem_1.createPublicClient)({
100
+ transport: (0, viem_1.http)(url, BATCH_TRANSPORT_OPTS),
101
+ });
102
+ clientCache.set(key, client);
103
+ }
104
+ return client;
105
+ }
106
+ /**
107
+ * Read several contract calls in one network round trip.
108
+ *
109
+ * Individual `readContract` calls issued together in the same tick are coalesced
110
+ * by the batching transport (see {@link BATCH_TRANSPORT_OPTS}). `Promise.all`
111
+ * dispatches them synchronously, which is what puts them in the same batch
112
+ * window — awaiting them one at a time would silently become N round trips.
113
+ *
114
+ * Failures are captured per call rather than thrown, because several PC20 probes
115
+ * are *expected* to revert: `pc20Metadata()` reverting on a plain ERC20 is the
116
+ * primary discriminator, not an error condition. Callers interpret each result
117
+ * individually via {@link unwrap}.
118
+ */
119
+ async function batchRead(client, contracts) {
120
+ exports.__readCount.n += 1;
121
+ return Promise.all(contracts.map((c) => client
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ .readContract(c)
124
+ .then((result) => ({ status: 'success', result }))
125
+ .catch((error) => ({ status: 'failure', error }))));
126
+ }
127
+ function unwrap(entry) {
128
+ return entry?.status === 'success' ? entry.result : undefined;
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // UniversalCore address
132
+ // ---------------------------------------------------------------------------
133
+ const universalCoreCache = new Map();
134
+ /**
135
+ * Resolve UniversalCore through the UGPC precompile, matching how
136
+ * `gas-calculator.queryOutboundGasFee` does it. Cached per network — the
137
+ * precompile's `universalCore` pointer does not move within a session.
138
+ */
139
+ async function getUniversalCoreAddress(opts) {
140
+ const cached = universalCoreCache.get(opts.network);
141
+ if (cached)
142
+ return cached;
143
+ const pushChain = (0, helpers_1.getPushChainForNetwork)(opts.network);
144
+ const client = clientFor(pushChain, opts);
145
+ exports.__readCount.n += 1;
146
+ const address = (await client.readContract({
147
+ address: (0, helpers_1.getUniversalGatewayPCAddress)(),
148
+ abi: abi_1.UNIVERSAL_GATEWAY_PC,
149
+ functionName: 'universalCore',
150
+ args: [],
151
+ }));
152
+ universalCoreCache.set(opts.network, address);
153
+ return address;
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Positive-only caches
157
+ // ---------------------------------------------------------------------------
158
+ /** wrapper → Push source. Positive entries only; see the cache policy above. */
159
+ const wrapperToSourceCache = new Map();
160
+ /** Push token → validated metadata. Positive entries only. */
161
+ const metadataCache = new Map();
162
+ /** `(network, chain, address)` pairs that have passed Tier B this session. */
163
+ const tierBVerified = new Set();
164
+ const cacheKey = (network, chain, address) => `${network}:${chain}:${address.toLowerCase()}`;
165
+ /** Test seam. Never call from product code — it would drop verified Tier B state. */
166
+ function __clearPC20Caches() {
167
+ wrapperToSourceCache.clear();
168
+ metadataCache.clear();
169
+ tierBVerified.clear();
170
+ universalCoreCache.clear();
171
+ clientCache.clear();
172
+ exports.__readCount.n = 0;
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Tier A — registry forward/reverse agreement
176
+ // ---------------------------------------------------------------------------
177
+ /**
178
+ * Resolve an external wrapper to its canonical Push-native source.
179
+ *
180
+ * One multicall: `getPC20Source(wrapper, ns)` and, in the same batch, the
181
+ * reverse `getPC20Wrapper` cannot be issued because it depends on the source.
182
+ * The reverse read is therefore a second round trip — this is the "2 sequential
183
+ * round trips" in the budget, and it is not collapsible: the registry has no
184
+ * combined accessor.
185
+ *
186
+ * @throws {PC20WrapperNotRegisteredError} when the wrapper is unknown.
187
+ * @throws {PC20RegistryMismatchError} when forward and reverse disagree.
188
+ */
189
+ async function resolveWrapperToSource(chain, address, opts) {
190
+ const namespace = (0, chain_namespace_1.chainToNamespace)(chain);
191
+ const rawWrapper = (0, address_codec_1.pc20AddressToBytes32)(chain, address);
192
+ const key = cacheKey(opts.network, chain, address);
193
+ const cached = wrapperToSourceCache.get(key);
194
+ if (cached && !opts.bypassCache) {
195
+ return { pushAddress: cached, chainNamespace: namespace, rawWrapper };
196
+ }
197
+ const core = await getUniversalCoreAddress(opts);
198
+ const pushClient = clientFor((0, helpers_1.getPushChainForNetwork)(opts.network), opts);
199
+ const [sourceEntry] = await batchRead(pushClient, [
200
+ {
201
+ address: core,
202
+ abi: abi_1.UNIVERSAL_CORE_EVM,
203
+ functionName: 'getPC20Source',
204
+ args: [rawWrapper, namespace],
205
+ },
206
+ ]);
207
+ const source = unwrap(sourceEntry);
208
+ if (!source || !source[1] || source[0] === '0x0000000000000000000000000000000000000000') {
209
+ throw new errors_1.PC20WrapperNotRegisteredError({
210
+ chain: String(chain),
211
+ address,
212
+ chainNamespace: namespace,
213
+ });
214
+ }
215
+ const pushAddress = (0, viem_1.getAddress)(source[0]);
216
+ // Reverse check. A stale or half-written entry would otherwise unlock the
217
+ // wrong Push token on burn.
218
+ const [wrapperEntry] = await batchRead(pushClient, [
219
+ {
220
+ address: core,
221
+ abi: abi_1.UNIVERSAL_CORE_EVM,
222
+ functionName: 'getPC20Wrapper',
223
+ args: [pushAddress, namespace],
224
+ },
225
+ ]);
226
+ const reverse = unwrap(wrapperEntry);
227
+ if (!reverse || (0, address_codec_1.isZeroBytes32)(reverse[0]) || reverse[0].toLowerCase() !== rawWrapper.toLowerCase()) {
228
+ throw new errors_1.PC20RegistryMismatchError({
229
+ chain: String(chain),
230
+ address,
231
+ chainNamespace: namespace,
232
+ resolvedWrapper: reverse && !(0, address_codec_1.isZeroBytes32)(reverse[0])
233
+ ? (0, address_codec_1.pc20Bytes32ToAddress)(chain, reverse[0])
234
+ : undefined,
235
+ });
236
+ }
237
+ wrapperToSourceCache.set(key, pushAddress);
238
+ return { pushAddress, chainNamespace: namespace, rawWrapper };
239
+ }
240
+ // ---------------------------------------------------------------------------
241
+ // Tier B — live factory identity (EVM)
242
+ // ---------------------------------------------------------------------------
243
+ /**
244
+ * Verify the live external gateway will actually take the PC20 burn path for
245
+ * this wrapper, and that its factory is the one UniversalCore knows about.
246
+ *
247
+ * Two multicalls: the gateway's factory pointer must be read before the
248
+ * factory-scoped calls can be addressed.
249
+ *
250
+ * @throws {PC20FactoryMismatchError} on any identity disagreement.
251
+ */
252
+ async function verifyEvmWrapperIdentity(chain, address, pushAddress, opts) {
253
+ const key = cacheKey(opts.network, chain, address);
254
+ if (!opts.strict && tierBVerified.has(key))
255
+ return;
256
+ const namespace = (0, chain_namespace_1.chainToNamespace)(chain);
257
+ const gateway = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[chain];
258
+ if (!gateway) {
259
+ throw new errors_1.UnsupportedPC20DestinationError({
260
+ chain: String(chain),
261
+ chainNamespace: namespace,
262
+ hint: 'No universal gateway address is configured for this chain.',
263
+ });
264
+ }
265
+ const core = await getUniversalCoreAddress(opts);
266
+ const pushClient = clientFor((0, helpers_1.getPushChainForNetwork)(opts.network), opts);
267
+ const extClient = clientFor(chain, opts);
268
+ // Reference factory to compare the gateway against. The deployed
269
+ // UniversalCore does not expose `pc20FactoryByChain` (chain-team decision:
270
+ // the token mappings are the supported registry surface), so the standing
271
+ // reference is the destination Vault's own pointer — the Vault is what
272
+ // deploys and mints wrappers (`Vault.sol:327`), so gateway-vs-Vault
273
+ // agreement is the identity that actually matters at settlement. The
274
+ // registry read stays first, failure-tolerant, for forward compatibility
275
+ // should a future upgrade add the accessor.
276
+ const [registryFactoryEntry] = await batchRead(pushClient, [
277
+ {
278
+ address: core,
279
+ abi: abi_1.UNIVERSAL_CORE_EVM,
280
+ functionName: 'pc20FactoryByChain',
281
+ args: [namespace],
282
+ },
283
+ ]);
284
+ const registryFactoryRaw = unwrap(registryFactoryEntry);
285
+ let referenceFactory = registryFactoryRaw && !(0, address_codec_1.isZeroBytes32)(registryFactoryRaw)
286
+ ? (0, address_codec_1.pc20Bytes32ToAddress)(chain, registryFactoryRaw)
287
+ : undefined;
288
+ if (!referenceFactory) {
289
+ const vault = chain_1.VAULT_ADDRESSES[chain];
290
+ if (vault) {
291
+ const [vaultFactoryEntry] = await batchRead(extClient, [
292
+ {
293
+ address: (0, viem_1.getAddress)(vault),
294
+ abi: abi_1.VAULT_PC20_FACTORY_EVM,
295
+ functionName: 'pc20Factory',
296
+ args: [],
297
+ },
298
+ ]);
299
+ const vaultFactory = unwrap(vaultFactoryEntry);
300
+ if (vaultFactory && vaultFactory !== '0x0000000000000000000000000000000000000000') {
301
+ referenceFactory = (0, viem_1.getAddress)(vaultFactory);
302
+ }
303
+ }
304
+ }
305
+ if (!referenceFactory) {
306
+ throw new errors_1.UnsupportedPC20DestinationError({
307
+ chain: String(chain),
308
+ chainNamespace: namespace,
309
+ hint: 'Neither UniversalCore nor the destination Vault has a PC20 factory ' +
310
+ 'registered for this chain.',
311
+ });
312
+ }
313
+ const [gatewayFactoryEntry, codeEntry] = await Promise.all([
314
+ batchRead(extClient, [
315
+ { address: gateway, abi: abi_1.EXTERNAL_GATEWAY_PC20_EVM, functionName: 'pc20Factory', args: [] },
316
+ ]).then((r) => r[0]),
317
+ (async () => {
318
+ exports.__readCount.n += 1;
319
+ const code = await extClient.getCode({ address: (0, viem_1.getAddress)(address) });
320
+ return { status: 'success', result: code };
321
+ })(),
322
+ ]);
323
+ const gatewayFactory = unwrap(gatewayFactoryEntry);
324
+ if (!gatewayFactory || (0, viem_1.getAddress)(gatewayFactory) !== (0, viem_1.getAddress)(referenceFactory)) {
325
+ throw new errors_1.PC20FactoryMismatchError({
326
+ chain: String(chain),
327
+ address,
328
+ chainNamespace: namespace,
329
+ gatewayFactory,
330
+ registryFactory: referenceFactory,
331
+ });
332
+ }
333
+ const code = unwrap(codeEntry);
334
+ if (!code || code === '0x') {
335
+ throw new errors_1.PC20FactoryMismatchError({
336
+ chain: String(chain),
337
+ address,
338
+ chainNamespace: namespace,
339
+ hint: 'No contract is deployed at this wrapper address.',
340
+ });
341
+ }
342
+ // Factory-scoped identity, batched into one round trip.
343
+ const factory = (0, viem_1.getAddress)(gatewayFactory);
344
+ const wrapper = (0, viem_1.getAddress)(address);
345
+ const [isWrapperEntry, wrapperSourceEntry, sourceAssetEntry] = await batchRead(extClient, [
346
+ { address: factory, abi: abi_1.PC20_FACTORY_EVM, functionName: 'isPC20Wrapper', args: [wrapper] },
347
+ { address: factory, abi: abi_1.PC20_FACTORY_EVM, functionName: 'wrapperToSource', args: [wrapper] },
348
+ { address: wrapper, abi: abi_1.PC20_WRAPPER_EVM, functionName: 'SOURCE_ASSET', args: [] },
349
+ ]);
350
+ if (unwrap(isWrapperEntry) !== true) {
351
+ throw new errors_1.PC20FactoryMismatchError({
352
+ chain: String(chain),
353
+ address,
354
+ chainNamespace: namespace,
355
+ hint: 'The live factory does not recognize this address as a PC20 wrapper.',
356
+ });
357
+ }
358
+ const factorySource = unwrap(wrapperSourceEntry);
359
+ const immutableSource = unwrap(sourceAssetEntry);
360
+ for (const [label, value] of [
361
+ ['factory.wrapperToSource', factorySource],
362
+ ['wrapper.SOURCE_ASSET', immutableSource],
363
+ ]) {
364
+ if (!value || (0, viem_1.getAddress)(value) !== (0, viem_1.getAddress)(pushAddress)) {
365
+ throw new errors_1.PC20FactoryMismatchError({
366
+ chain: String(chain),
367
+ address,
368
+ chainNamespace: namespace,
369
+ hint: `${label} does not match the Push source UniversalCore returned.`,
370
+ });
371
+ }
372
+ }
373
+ tierBVerified.add(key);
374
+ }
375
+ // ---------------------------------------------------------------------------
376
+ // Push-native metadata
377
+ // ---------------------------------------------------------------------------
378
+ /**
379
+ * Read and validate `pc20Metadata()` on a Push-native token.
380
+ *
381
+ * The absence of `pc20Metadata` is the discriminator between a PC20 and any
382
+ * other ERC20 — never the symbol. A synthetic PRC20 is detected separately so
383
+ * it gets an error that points at the right API rather than reading as
384
+ * "your token is broken".
385
+ */
386
+ async function readPushPC20Metadata(pushAddress, opts) {
387
+ const pushChain = (0, helpers_1.getPushChainForNetwork)(opts.network);
388
+ const key = cacheKey(opts.network, pushChain, pushAddress);
389
+ const cached = metadataCache.get(key);
390
+ if (cached)
391
+ return cached;
392
+ const client = clientFor(pushChain, opts);
393
+ const [metaEntry, codeEntry] = await Promise.all([
394
+ batchRead(client, [
395
+ { address: pushAddress, abi: abi_1.IPC20_EVM, functionName: 'pc20Metadata', args: [] },
396
+ ]).then((r) => r[0]),
397
+ (async () => {
398
+ exports.__readCount.n += 1;
399
+ return { status: 'success', result: await client.getCode({ address: pushAddress }) };
400
+ })(),
401
+ ]);
402
+ const code = unwrap(codeEntry);
403
+ if (!code || code === '0x') {
404
+ throw new errors_1.InvalidPC20MetadataError('No contract is deployed at this address.', {
405
+ chain: String(pushChain),
406
+ address: pushAddress,
407
+ });
408
+ }
409
+ const meta = unwrap(metaEntry);
410
+ if (!meta) {
411
+ if (await isSyntheticPRC20(pushAddress, opts)) {
412
+ throw new errors_1.PC20ExpectedButPRC20Error({
413
+ chain: String(pushChain),
414
+ address: pushAddress,
415
+ });
416
+ }
417
+ throw new errors_1.InvalidPC20MetadataError('Token does not implement pc20Metadata() and is not a Push-native PC20.', { chain: String(pushChain), address: pushAddress });
418
+ }
419
+ const [name, symbol, decimals, originAddress] = meta;
420
+ // The origin binding is what makes the metadata trustworthy: a contract can
421
+ // return any name it likes, but claiming to originate at a different address
422
+ // means it is not the canonical source for this token.
423
+ if ((0, viem_1.getAddress)(originAddress) !== (0, viem_1.getAddress)(pushAddress)) {
424
+ throw new errors_1.InvalidPC20MetadataError(`pc20Metadata().originAddress (${originAddress}) does not match the token address.`, { chain: String(pushChain), address: pushAddress });
425
+ }
426
+ if (!name || !symbol) {
427
+ throw new errors_1.InvalidPC20MetadataError('PC20 name and symbol must be non-empty.', {
428
+ chain: String(pushChain),
429
+ address: pushAddress,
430
+ });
431
+ }
432
+ const validated = {
433
+ name,
434
+ symbol,
435
+ decimals: Number(decimals),
436
+ originAddress: (0, viem_1.getAddress)(originAddress),
437
+ };
438
+ metadataCache.set(key, validated);
439
+ return validated;
440
+ }
441
+ /**
442
+ * Distinguish a synthetic PRC20 from an arbitrary ERC20.
443
+ *
444
+ * PRC20s expose the origin chain they wrap; PC20s do not. Checked only after
445
+ * `pc20Metadata()` has already failed, so it costs nothing on the happy path.
446
+ */
447
+ async function isSyntheticPRC20(address, opts) {
448
+ const client = clientFor((0, helpers_1.getPushChainForNetwork)(opts.network), opts);
449
+ const [entry] = await batchRead(client, [
450
+ {
451
+ address,
452
+ abi: [
453
+ {
454
+ type: 'function',
455
+ name: 'CHAIN_NAMESPACE',
456
+ inputs: [],
457
+ outputs: [{ name: '', type: 'string' }],
458
+ stateMutability: 'view',
459
+ },
460
+ ],
461
+ functionName: 'CHAIN_NAMESPACE',
462
+ args: [],
463
+ },
464
+ ]);
465
+ return typeof unwrap(entry) === 'string';
466
+ }
467
+ // ---------------------------------------------------------------------------
468
+ // Destination-side lookups
469
+ // ---------------------------------------------------------------------------
470
+ /**
471
+ * Confirmed wrapper deployments for a Push-native PC20.
472
+ *
473
+ * One multicall regardless of chain count — every `getPC20Wrapper` read targets
474
+ * UniversalCore on Push Chain, so fanning out per chain would be N round trips
475
+ * to the same endpoint for no reason.
476
+ *
477
+ * Returns confirmed deployments only. A predicted first-export address is the
478
+ * outbound builder's concern, not discovery's.
479
+ */
480
+ async function listPC20Deployments(pushAddress, opts) {
481
+ const targets = (opts.chains ?? (0, chain_namespace_1.allExternalChains)()).filter((c) => !(0, chain_namespace_1.isPushChain)(c));
482
+ if (targets.length === 0)
483
+ return [];
484
+ const core = await getUniversalCoreAddress(opts);
485
+ const client = clientFor((0, helpers_1.getPushChainForNetwork)(opts.network), opts);
486
+ const namespaces = targets.map((chain) => (0, chain_namespace_1.chainToNamespace)(chain));
487
+ const results = await batchRead(client, targets.map((_, i) => ({
488
+ address: core,
489
+ abi: abi_1.UNIVERSAL_CORE_EVM,
490
+ functionName: 'getPC20Wrapper',
491
+ args: [pushAddress, namespaces[i]],
492
+ })));
493
+ const deployments = [];
494
+ targets.forEach((chain, i) => {
495
+ const value = unwrap(results[i]);
496
+ if (!value)
497
+ return;
498
+ const [rawAddress, deployed] = value;
499
+ if (!deployed || (0, address_codec_1.isZeroBytes32)(rawAddress))
500
+ return;
501
+ deployments.push({
502
+ chain,
503
+ chainNamespace: namespaces[i],
504
+ vm: (0, chain_namespace_1.vmForChain)(chain),
505
+ address: (0, address_codec_1.pc20Bytes32ToAddress)(chain, rawAddress),
506
+ rawAddress,
507
+ });
508
+ });
509
+ return deployments;
510
+ }
511
+ /**
512
+ * Registered wrapper for a Push PC20 on one destination chain.
513
+ *
514
+ * `undefined` means not yet deployed — a legitimate state before a first
515
+ * export, not an error. Callers that need an address for a first export must go
516
+ * through the prediction path, which verifies against the live factory.
517
+ */
518
+ async function getRegisteredWrapper(pushAddress, destinationChain, opts) {
519
+ const [found] = await listPC20Deployments(pushAddress, {
520
+ ...opts,
521
+ chains: [destinationChain],
522
+ });
523
+ return found;
524
+ }
525
+ // ---------------------------------------------------------------------------
526
+ // Chain discovery (lookup utility only)
527
+ // ---------------------------------------------------------------------------
528
+ /**
529
+ * Work out which chain an address belongs to, when the caller did not say.
530
+ *
531
+ * Used ONLY by the read-only `getPC20Address` utility. The transaction path
532
+ * never calls this — `funds.token` requires an explicit `chain`, and
533
+ * `gate.ts` rejects one that disagrees with where the funds actually are. A
534
+ * wrapper address copied from the wrong chain still fails on send; it merely
535
+ * resolves informationally on lookup.
536
+ *
537
+ * Order is cheapest-first:
538
+ * 1. narrow candidates by address format (local, free);
539
+ * 2. try Push-native, which short-circuits with no registry read at all;
540
+ * 3. probe every remaining namespace in one multicall.
541
+ *
542
+ * @throws {PC20AmbiguousAddressError} when several chains claim the address
543
+ * with different Push sources.
544
+ */
545
+ async function discoverPC20Chain(address, opts) {
546
+ const pushChain = (0, helpers_1.getPushChainForNetwork)(opts.network);
547
+ const looksEvm = /^0x[0-9a-fA-F]{40}$/.test(address);
548
+ // 2. Push-native short-circuit. Only an EVM-shaped address can be one.
549
+ if (looksEvm) {
550
+ try {
551
+ const pushAddress = (0, viem_1.getAddress)(address);
552
+ await readPushPC20Metadata(pushAddress, opts);
553
+ return { chain: pushChain, pushAddress };
554
+ }
555
+ catch {
556
+ // Not a Push-native PC20 — fall through to the external probe.
557
+ }
558
+ }
559
+ // 1. Format narrowing. A hex address cannot be a Solana mint and vice versa,
560
+ // so probing the other VM's namespaces would be pure waste.
561
+ const candidates = (0, chain_namespace_1.allExternalChains)().filter((chain) => looksEvm ? (0, chain_namespace_1.vmForChain)(chain) === enums_1.VM.EVM : (0, chain_namespace_1.vmForChain)(chain) === enums_1.VM.SVM);
562
+ if (candidates.length === 0)
563
+ return null;
564
+ // 3. One multicall across every candidate namespace — all these reads hit
565
+ // UniversalCore on Push, so chain count costs nothing.
566
+ const core = await getUniversalCoreAddress(opts);
567
+ const client = clientFor(pushChain, opts);
568
+ const probes = candidates.flatMap((chain) => {
569
+ try {
570
+ return [
571
+ {
572
+ chain,
573
+ namespace: (0, chain_namespace_1.chainToNamespace)(chain),
574
+ raw: (0, address_codec_1.pc20AddressToBytes32)(chain, address),
575
+ },
576
+ ];
577
+ }
578
+ catch {
579
+ // Address is malformed for this chain's VM — not a candidate.
580
+ return [];
581
+ }
582
+ });
583
+ if (probes.length === 0)
584
+ return null;
585
+ const results = await batchRead(client, probes.map((p) => ({
586
+ address: core,
587
+ abi: abi_1.UNIVERSAL_CORE_EVM,
588
+ functionName: 'getPC20Source',
589
+ args: [p.raw, p.namespace],
590
+ })));
591
+ const matches = [];
592
+ probes.forEach((p, i) => {
593
+ const value = unwrap(results[i]);
594
+ if (!value)
595
+ return;
596
+ const [source, known] = value;
597
+ if (!known || source === '0x0000000000000000000000000000000000000000')
598
+ return;
599
+ matches.push({ chain: p.chain, pushAddress: (0, viem_1.getAddress)(source) });
600
+ });
601
+ if (matches.length === 0)
602
+ return null;
603
+ // Several chains can legitimately hold the same wrapper address only if they
604
+ // resolve to the same token. Different sources means we cannot answer.
605
+ const distinct = new Set(matches.map((m) => m.pushAddress.toLowerCase()));
606
+ if (distinct.size > 1) {
607
+ throw new errors_1.PC20AmbiguousAddressError(address, matches.map((m) => ({ chain: String(m.chain), pushAddress: m.pushAddress })));
608
+ }
609
+ return matches[0];
610
+ }
611
+ // ---------------------------------------------------------------------------
612
+ // Top-level resolution
613
+ // ---------------------------------------------------------------------------
614
+ /**
615
+ * Resolve a `{ chain, address }` PC20 reference into everything the execution
616
+ * path needs, exactly once per transaction.
617
+ *
618
+ * `expectedChain` is where the funds actually are — the signer's chain for a
619
+ * direct external route, `params.from.chain` for a CEA-origin route, the Push
620
+ * chain for an export. Chain-ownership validation is the caller's job because
621
+ * only the caller knows which of those applies; this function assumes it has
622
+ * already passed.
623
+ */
624
+ async function resolvePC20Token(chain, address, opts) {
625
+ const pushChain = (0, helpers_1.getPushChainForNetwork)(opts.network);
626
+ // Export: the caller named a Push-native token.
627
+ if ((0, chain_namespace_1.isPushChain)(chain)) {
628
+ const pushAddress = (0, viem_1.getAddress)(address);
629
+ const meta = await readPushPC20Metadata(pushAddress, opts);
630
+ return {
631
+ direction: 'export',
632
+ originChain: pushChain,
633
+ originAddress: pushAddress,
634
+ pushAddress,
635
+ name: meta.name,
636
+ symbol: meta.symbol,
637
+ decimals: meta.decimals,
638
+ };
639
+ }
640
+ // Import: the caller named an external wrapper.
641
+ const { pushAddress, chainNamespace } = await resolveWrapperToSource(chain, address, opts);
642
+ if ((0, chain_namespace_1.vmForChain)(chain) === enums_1.VM.EVM && (opts.tierB || opts.strict)) {
643
+ await verifyEvmWrapperIdentity(chain, address, pushAddress, opts);
644
+ }
645
+ const meta = await readPushPC20Metadata(pushAddress, opts);
646
+ return {
647
+ direction: 'import',
648
+ originChain: chain,
649
+ originAddress: (0, chain_namespace_1.vmForChain)(chain) === enums_1.VM.EVM ? (0, viem_1.getAddress)(address) : address,
650
+ pushAddress,
651
+ name: meta.name,
652
+ symbol: meta.symbol,
653
+ decimals: meta.decimals,
654
+ wrapperAddress: (0, chain_namespace_1.vmForChain)(chain) === enums_1.VM.EVM ? (0, viem_1.getAddress)(address) : address,
655
+ chainNamespace,
656
+ };
657
+ }
658
+ //# sourceMappingURL=resolver.js.map