@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.
- package/CHANGELOG.md +48 -0
- package/package.json +1 -1
- package/src/lib/cache/cache.js +2 -0
- package/src/lib/constants/abi/index.d.ts +1 -0
- package/src/lib/constants/abi/index.js +8 -1
- package/src/lib/constants/abi/pc20.evm.d.ts +217 -0
- package/src/lib/constants/abi/pc20.evm.js +150 -0
- package/src/lib/constants/abi/prc20.evm.d.ts +172 -0
- package/src/lib/constants/abi/prc20.evm.js +97 -0
- package/src/lib/constants/abi/universalGatewayV0.json +1 -1
- package/src/lib/constants/chain.js +38 -8
- package/src/lib/constants/selectors.js +2 -2
- package/src/lib/constants/tokens.js +4 -3
- package/src/lib/formatters.js +16 -6
- package/src/lib/generated/uexecutor/v1/query.js +2 -4
- package/src/lib/generated/uexecutor/v1/types.d.ts +14 -0
- package/src/lib/generated/uexecutor/v1/types.js +90 -47
- package/src/lib/generated/uexecutor/v2/query.js +2 -4
- package/src/lib/generated/uexecutor/v2/types.d.ts +20 -0
- package/src/lib/generated/uexecutor/v2/types.js +49 -33
- package/src/lib/generated/v1/tx.js +30 -37
- package/src/lib/index.d.ts +3 -0
- package/src/lib/index.js +21 -3
- package/src/lib/internal/buffer.js +1 -2
- package/src/lib/orchestrator/cea-utils.js +73 -83
- package/src/lib/orchestrator/internals/account-manager.js +130 -139
- package/src/lib/orchestrator/internals/cascade.d.ts +0 -6
- package/src/lib/orchestrator/internals/cascade.js +1184 -1059
- package/src/lib/orchestrator/internals/confirmation.js +138 -148
- package/src/lib/orchestrator/internals/error-decoder.js +1 -1
- package/src/lib/orchestrator/internals/errors.js +12 -3
- package/src/lib/orchestrator/internals/execute-funds-only.d.ts +2 -2
- package/src/lib/orchestrator/internals/execute-funds-only.js +297 -259
- package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +2 -2
- package/src/lib/orchestrator/internals/execute-funds-payload.js +220 -220
- package/src/lib/orchestrator/internals/execute-standard.d.ts +0 -8
- package/src/lib/orchestrator/internals/execute-standard.js +339 -342
- package/src/lib/orchestrator/internals/gas-calculator.js +497 -520
- package/src/lib/orchestrator/internals/gas-usd-sizer.js +101 -110
- package/src/lib/orchestrator/internals/gateway-client.js +102 -103
- package/src/lib/orchestrator/internals/helpers.js +30 -27
- package/src/lib/orchestrator/internals/inbound-tracker.js +234 -238
- package/src/lib/orchestrator/internals/outbound-sync.js +463 -452
- package/src/lib/orchestrator/internals/outbound-tracker.js +27 -34
- package/src/lib/orchestrator/internals/payload-builder.d.ts +2 -2
- package/src/lib/orchestrator/internals/payload-builder.js +119 -100
- package/src/lib/orchestrator/internals/pc-usd-oracle.js +121 -129
- package/src/lib/orchestrator/internals/pc20/address-codec.d.ts +31 -0
- package/src/lib/orchestrator/internals/pc20/address-codec.js +124 -0
- package/src/lib/orchestrator/internals/pc20/chain-namespace.d.ts +68 -0
- package/src/lib/orchestrator/internals/pc20/chain-namespace.js +136 -0
- package/src/lib/orchestrator/internals/pc20/errors.d.ts +160 -0
- package/src/lib/orchestrator/internals/pc20/errors.js +236 -0
- package/src/lib/orchestrator/internals/pc20/export.d.ts +172 -0
- package/src/lib/orchestrator/internals/pc20/export.js +412 -0
- package/src/lib/orchestrator/internals/pc20/gate.d.ts +67 -0
- package/src/lib/orchestrator/internals/pc20/gate.js +184 -0
- package/src/lib/orchestrator/internals/pc20/resolver.d.ts +187 -0
- package/src/lib/orchestrator/internals/pc20/resolver.js +658 -0
- package/src/lib/orchestrator/internals/pc20/svm.d.ts +189 -0
- package/src/lib/orchestrator/internals/pc20/svm.js +286 -0
- package/src/lib/orchestrator/internals/pc20/tracking.d.ts +46 -0
- package/src/lib/orchestrator/internals/pc20/tracking.js +109 -0
- package/src/lib/orchestrator/internals/preflight.js +1 -1
- package/src/lib/orchestrator/internals/progress-route-hooks.js +2 -2
- package/src/lib/orchestrator/internals/push-chain-tx.js +319 -329
- package/src/lib/orchestrator/internals/quote.js +90 -94
- package/src/lib/orchestrator/internals/rescue.js +76 -79
- package/src/lib/orchestrator/internals/response-builder.d.ts +28 -1
- package/src/lib/orchestrator/internals/response-builder.js +817 -701
- package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -15
- package/src/lib/orchestrator/internals/route-handlers.js +1512 -1178
- package/src/lib/orchestrator/internals/signing.js +78 -83
- package/src/lib/orchestrator/internals/svm-bridge.d.ts +2 -2
- package/src/lib/orchestrator/internals/svm-bridge.js +161 -93
- package/src/lib/orchestrator/internals/svm-helpers.d.ts +15 -1
- package/src/lib/orchestrator/internals/svm-helpers.js +68 -24
- package/src/lib/orchestrator/internals/svm-rent.d.ts +19 -1
- package/src/lib/orchestrator/internals/svm-rent.js +163 -141
- package/src/lib/orchestrator/internals/tx-transformer.js +26 -23
- package/src/lib/orchestrator/internals/uea-manager.js +89 -86
- package/src/lib/orchestrator/max-pc-for-gas.js +5 -6
- package/src/lib/orchestrator/orchestrator.js +217 -200
- package/src/lib/orchestrator/orchestrator.types.d.ts +121 -1
- package/src/lib/orchestrator/orchestrator.types.js +19 -0
- package/src/lib/orchestrator/payload-builders.d.ts +2 -2
- package/src/lib/orchestrator/payload-builders.js +26 -24
- package/src/lib/orchestrator/route-detector.js +26 -9
- package/src/lib/orchestrator/svm-idl/ix-encoder.js +1 -2
- package/src/lib/orchestrator/svm-idl/normalize-address.js +2 -2
- package/src/lib/orchestrator/svm-idl/resolve.js +1 -2
- package/src/lib/price-fetch/price-fetch.js +67 -68
- package/src/lib/progress-hook/progress-hook.js +124 -72
- package/src/lib/push-chain/push-chain.js +171 -153
- package/src/lib/push-client/push-client.js +223 -237
- package/src/lib/universal/account/account.js +349 -365
- package/src/lib/universal/signer/signer.js +306 -294
- package/src/lib/universal-tx-detector/auto-detect.js +55 -60
- package/src/lib/universal-tx-detector/cascade.js +160 -172
- package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
- package/src/lib/universal-tx-detector/classify.js +2 -3
- package/src/lib/universal-tx-detector/detector-svm.js +139 -147
- package/src/lib/universal-tx-detector/detector.js +145 -157
- package/src/lib/universal-tx-detector/normalize.js +7 -7
- package/src/lib/utils/external-tx-hash.js +1 -2
- package/src/lib/utils.d.ts +44 -0
- package/src/lib/utils.js +762 -683
- package/src/lib/vm-client/evm-client.js +186 -202
- package/src/lib/vm-client/svm-client.d.ts +1 -1
- package/src/lib/vm-client/svm-client.js +220 -224
- 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
|