@pushchain/core 6.0.20 → 6.0.22

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