@pushchain/core 6.0.21 → 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.
- package/CHANGELOG.md +21 -2
- package/package.json +8 -2
- package/src/lib/constants/abi/erc20.evm.d.ts +30 -0
- package/src/lib/constants/abi/erc20.evm.js +21 -0
- package/src/lib/constants/abi/index.d.ts +1 -1
- package/src/lib/constants/abi/index.js +1 -2
- package/src/lib/constants/abi/pc20.evm.d.ts +0 -25
- package/src/lib/constants/abi/pc20.evm.js +1 -17
- package/src/lib/orchestrator/cea-utils.js +19 -3
- package/src/lib/orchestrator/internals/pc20/errors.d.ts +1 -1
- package/src/lib/orchestrator/internals/pc20/errors.js +1 -1
- package/src/lib/orchestrator/internals/pc20/export.d.ts +8 -4
- package/src/lib/orchestrator/internals/pc20/export.js +37 -18
- package/src/lib/orchestrator/internals/pc20/resolver.d.ts +5 -7
- package/src/lib/orchestrator/internals/pc20/resolver.js +46 -55
- package/src/lib/orchestrator/internals/route-handlers.js +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
@pushchain/core@6.0.22 (2026-08-14)
|
|
2
|
+
|
|
3
|
+
- fix: make PC20 first exports and Solana R3 E2E reliable
|
|
4
|
+
- fix: support PC20 exports for standard ERC-20 tokens
|
|
5
|
+
- feat: curated E2E CI suite with pre-flight fund gate
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
1
9
|
@pushchain/core@6.0.21 (2026-08-01)
|
|
2
10
|
|
|
3
11
|
- fix: pc20 bug fix
|
|
@@ -11,8 +19,15 @@
|
|
|
11
19
|
@pushchain/core@6.1.0 (unreleased)
|
|
12
20
|
|
|
13
21
|
- Added first-class PC20 support to the universal transaction API. PC20 tokens
|
|
14
|
-
are
|
|
15
|
-
|
|
22
|
+
are identified by chain and address without an explicit token-standard flag.
|
|
23
|
+
External wrappers are resolved through UniversalCore, while Push-native
|
|
24
|
+
tokens are validated directly on Push Chain.
|
|
25
|
+
|
|
26
|
+
- Removed the custom `pc20Metadata()` requirement for Push-native PC20 exports.
|
|
27
|
+
Any metadata-compatible ERC-20 born on Push Chain can now be exported: the SDK
|
|
28
|
+
reads the standard `name()`, `symbol()`, and `decimals()` functions, verifies
|
|
29
|
+
deployed bytecode, and continues to reject synthetic PRC20 addresses from the
|
|
30
|
+
PC20 path.
|
|
16
31
|
|
|
17
32
|
- Added `PushChain.utils.tokens.getPC20Address()` for resolving either a
|
|
18
33
|
Push-native PC20 or an external wrapper and listing its confirmed deployments.
|
|
@@ -40,6 +55,10 @@
|
|
|
40
55
|
account as the gateway recipient instead of the zero address, preventing the
|
|
41
56
|
forwarded funds leg from attempting to credit `address(0)`.
|
|
42
57
|
|
|
58
|
+
- Fixed first PC20 wrapper deployments running out of gas on the destination.
|
|
59
|
+
The SDK now re-quotes UGPC with the resolved deployment-aware limit and writes
|
|
60
|
+
the corresponding explicit limit into the outbound request.
|
|
61
|
+
|
|
43
62
|
- Realigned inbound protobuf fields with the chain, exposed PC20 inbound
|
|
44
63
|
metadata, and added SVM remaining-account support required by the Solana
|
|
45
64
|
gateway.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushchain/core",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.22",
|
|
4
4
|
"homepage": "https://push.org",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushchain",
|
|
@@ -50,7 +50,11 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
|
-
"build:proto": "scripts/protoc-generate.sh"
|
|
53
|
+
"build:proto": "scripts/protoc-generate.sh",
|
|
54
|
+
"e2e:list": "ts-node --transpile-only __e2e__/ci/run.ts --list",
|
|
55
|
+
"e2e:verify": "ts-node --transpile-only __e2e__/ci/run.ts --verify",
|
|
56
|
+
"e2e:preflight": "ts-node --transpile-only __e2e__/ci/preflight.ts",
|
|
57
|
+
"e2e:ci": "ts-node --transpile-only __e2e__/ci/run.ts"
|
|
54
58
|
},
|
|
55
59
|
"type": "commonjs",
|
|
56
60
|
"main": "./src/index.js",
|
|
@@ -67,7 +71,9 @@
|
|
|
67
71
|
],
|
|
68
72
|
"devDependencies": {
|
|
69
73
|
"@types/bs58": "^4.0.0",
|
|
74
|
+
"dotenv": "^16.4.5",
|
|
70
75
|
"secp256k1": "^5.0.1",
|
|
76
|
+
"ts-node": "10.9.1",
|
|
71
77
|
"ts-proto": "^2.0.3"
|
|
72
78
|
}
|
|
73
79
|
}
|
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
export declare const ERC20_EVM: readonly [{
|
|
2
|
+
readonly type: "function";
|
|
3
|
+
readonly name: "name";
|
|
4
|
+
readonly inputs: readonly [];
|
|
5
|
+
readonly outputs: readonly [{
|
|
6
|
+
readonly name: "";
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
readonly internalType: "string";
|
|
9
|
+
}];
|
|
10
|
+
readonly stateMutability: "view";
|
|
11
|
+
}, {
|
|
12
|
+
readonly type: "function";
|
|
13
|
+
readonly name: "symbol";
|
|
14
|
+
readonly inputs: readonly [];
|
|
15
|
+
readonly outputs: readonly [{
|
|
16
|
+
readonly name: "";
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
readonly internalType: "string";
|
|
19
|
+
}];
|
|
20
|
+
readonly stateMutability: "view";
|
|
21
|
+
}, {
|
|
22
|
+
readonly type: "function";
|
|
23
|
+
readonly name: "decimals";
|
|
24
|
+
readonly inputs: readonly [];
|
|
25
|
+
readonly outputs: readonly [{
|
|
26
|
+
readonly name: "";
|
|
27
|
+
readonly type: "uint8";
|
|
28
|
+
readonly internalType: "uint8";
|
|
29
|
+
}];
|
|
30
|
+
readonly stateMutability: "view";
|
|
31
|
+
}, {
|
|
2
32
|
readonly type: "function";
|
|
3
33
|
readonly name: "balanceOf";
|
|
4
34
|
readonly inputs: readonly [{
|
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ERC20_EVM = void 0;
|
|
4
4
|
exports.ERC20_EVM = [
|
|
5
|
+
{
|
|
6
|
+
type: 'function',
|
|
7
|
+
name: 'name',
|
|
8
|
+
inputs: [],
|
|
9
|
+
outputs: [{ name: '', type: 'string', internalType: 'string' }],
|
|
10
|
+
stateMutability: 'view',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
type: 'function',
|
|
14
|
+
name: 'symbol',
|
|
15
|
+
inputs: [],
|
|
16
|
+
outputs: [{ name: '', type: 'string', internalType: 'string' }],
|
|
17
|
+
stateMutability: 'view',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'function',
|
|
21
|
+
name: 'decimals',
|
|
22
|
+
inputs: [],
|
|
23
|
+
outputs: [{ name: '', type: 'uint8', internalType: 'uint8' }],
|
|
24
|
+
stateMutability: 'view',
|
|
25
|
+
},
|
|
5
26
|
{
|
|
6
27
|
type: 'function',
|
|
7
28
|
name: 'balanceOf',
|
|
@@ -12,6 +12,6 @@ export { CEA_FACTORY_EVM } from './ceaFactory.evm';
|
|
|
12
12
|
export { UNIVERSAL_GATEWAY_PC } from './universalGatewayPC.evm';
|
|
13
13
|
export { UNIVERSAL_CORE_EVM } from './prc20.evm';
|
|
14
14
|
export { UEA_FACTORY_ABI } from './uea-factory';
|
|
15
|
-
export {
|
|
15
|
+
export { PC20_FACTORY_EVM, PC20_WRAPPER_EVM, EXTERNAL_GATEWAY_PC20_EVM, VAULT_PC20_FACTORY_EVM, } from './pc20.evm';
|
|
16
16
|
export { WPC_EVM } from './wpc.evm';
|
|
17
17
|
export { UNIV3_SWAP_ROUTER_EVM } from './uniswap-v3-router.evm';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UNIV3_SWAP_ROUTER_EVM = exports.WPC_EVM = exports.VAULT_PC20_FACTORY_EVM = exports.EXTERNAL_GATEWAY_PC20_EVM = exports.PC20_WRAPPER_EVM = exports.PC20_FACTORY_EVM = exports.
|
|
3
|
+
exports.UNIV3_SWAP_ROUTER_EVM = exports.WPC_EVM = exports.VAULT_PC20_FACTORY_EVM = exports.EXTERNAL_GATEWAY_PC20_EVM = exports.PC20_WRAPPER_EVM = exports.PC20_FACTORY_EVM = exports.UEA_FACTORY_ABI = exports.UNIVERSAL_CORE_EVM = exports.UNIVERSAL_GATEWAY_PC = exports.CEA_FACTORY_EVM = exports.CEA_EVM = exports.SVM_GATEWAY_IDL = exports.ERC20_EVM = exports.UNIVERSAL_GATEWAY_V1_SEND = exports.UNIVERSAL_GATEWAY_V0 = exports.UEA_SVM = exports.UEA_EVM = exports.FACTORY_V1 = exports.FEE_LOCKER_SVM = exports.FEE_LOCKER_EVM = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
var feeLocker_evm_1 = require("./feeLocker.evm");
|
|
6
6
|
Object.defineProperty(exports, "FEE_LOCKER_EVM", { enumerable: true, get: function () { return feeLocker_evm_1.FEE_LOCKER_EVM; } });
|
|
@@ -33,7 +33,6 @@ var uea_factory_1 = require("./uea-factory");
|
|
|
33
33
|
Object.defineProperty(exports, "UEA_FACTORY_ABI", { enumerable: true, get: function () { return uea_factory_1.UEA_FACTORY_ABI; } });
|
|
34
34
|
// PC20
|
|
35
35
|
var pc20_evm_1 = require("./pc20.evm");
|
|
36
|
-
Object.defineProperty(exports, "IPC20_EVM", { enumerable: true, get: function () { return pc20_evm_1.IPC20_EVM; } });
|
|
37
36
|
Object.defineProperty(exports, "PC20_FACTORY_EVM", { enumerable: true, get: function () { return pc20_evm_1.PC20_FACTORY_EVM; } });
|
|
38
37
|
Object.defineProperty(exports, "PC20_WRAPPER_EVM", { enumerable: true, get: function () { return pc20_evm_1.PC20_WRAPPER_EVM; } });
|
|
39
38
|
Object.defineProperty(exports, "EXTERNAL_GATEWAY_PC20_EVM", { enumerable: true, get: function () { return pc20_evm_1.EXTERNAL_GATEWAY_PC20_EVM; } });
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* PC20 ABIs — minimal read surfaces for registry resolution and validation.
|
|
3
3
|
*
|
|
4
4
|
* Sources:
|
|
5
|
-
* IPC20 push-chain-gateway-contracts/contracts/evm-gateway/src/interfaces/IPC20.sol
|
|
6
5
|
* PC20Factory push-chain-gateway-contracts/contracts/evm-gateway/src/PC20Factory.sol
|
|
7
6
|
* PC20Wrapper push-chain-gateway-contracts/contracts/evm-gateway/src/PC20Wrapper.sol
|
|
8
7
|
* UniversalGateway push-chain-gateway-contracts/contracts/evm-gateway/src/UniversalGateway.sol
|
|
@@ -10,30 +9,6 @@
|
|
|
10
9
|
* Read-only entries only. Writes go through the existing gateway ABIs — PC20
|
|
11
10
|
* changes the payload, not the entrypoint.
|
|
12
11
|
*/
|
|
13
|
-
/** Push-native token eligible for export. Presence of `pc20Metadata` IS the PC20 test. */
|
|
14
|
-
export declare const IPC20_EVM: readonly [{
|
|
15
|
-
readonly type: "function";
|
|
16
|
-
readonly name: "pc20Metadata";
|
|
17
|
-
readonly inputs: readonly [];
|
|
18
|
-
readonly outputs: readonly [{
|
|
19
|
-
readonly name: "name";
|
|
20
|
-
readonly type: "string";
|
|
21
|
-
readonly internalType: "string";
|
|
22
|
-
}, {
|
|
23
|
-
readonly name: "symbol";
|
|
24
|
-
readonly type: "string";
|
|
25
|
-
readonly internalType: "string";
|
|
26
|
-
}, {
|
|
27
|
-
readonly name: "decimals";
|
|
28
|
-
readonly type: "uint8";
|
|
29
|
-
readonly internalType: "uint8";
|
|
30
|
-
}, {
|
|
31
|
-
readonly name: "originAddress";
|
|
32
|
-
readonly type: "address";
|
|
33
|
-
readonly internalType: "address";
|
|
34
|
-
}];
|
|
35
|
-
readonly stateMutability: "view";
|
|
36
|
-
}];
|
|
37
12
|
/**
|
|
38
13
|
* Destination-chain factory.
|
|
39
14
|
*
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* PC20 ABIs — minimal read surfaces for registry resolution and validation.
|
|
4
4
|
*
|
|
5
5
|
* Sources:
|
|
6
|
-
* IPC20 push-chain-gateway-contracts/contracts/evm-gateway/src/interfaces/IPC20.sol
|
|
7
6
|
* PC20Factory push-chain-gateway-contracts/contracts/evm-gateway/src/PC20Factory.sol
|
|
8
7
|
* PC20Wrapper push-chain-gateway-contracts/contracts/evm-gateway/src/PC20Wrapper.sol
|
|
9
8
|
* UniversalGateway push-chain-gateway-contracts/contracts/evm-gateway/src/UniversalGateway.sol
|
|
@@ -12,22 +11,7 @@
|
|
|
12
11
|
* changes the payload, not the entrypoint.
|
|
13
12
|
*/
|
|
14
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.EXTERNAL_GATEWAY_PC20_EVM = exports.VAULT_PC20_FACTORY_EVM = exports.PC20_WRAPPER_EVM = exports.PC20_FACTORY_EVM =
|
|
16
|
-
/** Push-native token eligible for export. Presence of `pc20Metadata` IS the PC20 test. */
|
|
17
|
-
exports.IPC20_EVM = [
|
|
18
|
-
{
|
|
19
|
-
type: 'function',
|
|
20
|
-
name: 'pc20Metadata',
|
|
21
|
-
inputs: [],
|
|
22
|
-
outputs: [
|
|
23
|
-
{ name: 'name', type: 'string', internalType: 'string' },
|
|
24
|
-
{ name: 'symbol', type: 'string', internalType: 'string' },
|
|
25
|
-
{ name: 'decimals', type: 'uint8', internalType: 'uint8' },
|
|
26
|
-
{ name: 'originAddress', type: 'address', internalType: 'address' },
|
|
27
|
-
],
|
|
28
|
-
stateMutability: 'view',
|
|
29
|
-
},
|
|
30
|
-
];
|
|
14
|
+
exports.EXTERNAL_GATEWAY_PC20_EVM = exports.VAULT_PC20_FACTORY_EVM = exports.PC20_WRAPPER_EVM = exports.PC20_FACTORY_EVM = void 0;
|
|
31
15
|
/**
|
|
32
16
|
* Destination-chain factory.
|
|
33
17
|
*
|
|
@@ -36,6 +36,22 @@ function getViemChain(chain) {
|
|
|
36
36
|
}
|
|
37
37
|
return viemChain;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Transport for a CEAFactory read.
|
|
41
|
+
*
|
|
42
|
+
* `http(undefined)` falls back to the *viem* chain's default endpoint, which for
|
|
43
|
+
* `sepolia` is `https://sepolia.drpc.org` — a paid-plan host that answers every
|
|
44
|
+
* request with HTTP 400 ("chain is not available on free plan"). That silently
|
|
45
|
+
* broke every CEA derivation that did not pass an explicit rpcUrl, and no caller
|
|
46
|
+
* passes one. Use the SDK's own `CHAIN_INFO[].defaultRPC` list instead, wrapped in
|
|
47
|
+
* `fallback` so one dead endpoint rotates to the next.
|
|
48
|
+
*/
|
|
49
|
+
function ceaTransport(chain, rpcUrl) {
|
|
50
|
+
if (rpcUrl)
|
|
51
|
+
return (0, viem_1.http)(rpcUrl);
|
|
52
|
+
const rpcs = chain_1.CHAIN_INFO[chain]?.defaultRPC ?? [];
|
|
53
|
+
return rpcs.length > 0 ? (0, viem_1.fallback)(rpcs.map((url) => (0, viem_1.http)(url))) : (0, viem_1.http)();
|
|
54
|
+
}
|
|
39
55
|
/**
|
|
40
56
|
* Get CEA address for a UEA on a specific chain
|
|
41
57
|
*
|
|
@@ -58,7 +74,7 @@ async function getCEAAddress(ueaAddress, chain, rpcUrl) {
|
|
|
58
74
|
const viemChain = getViemChain(chain);
|
|
59
75
|
const client = (0, viem_1.createPublicClient)({
|
|
60
76
|
chain: viemChain,
|
|
61
|
-
transport: (
|
|
77
|
+
transport: ceaTransport(chain, rpcUrl),
|
|
62
78
|
});
|
|
63
79
|
if (cachedAddress) {
|
|
64
80
|
// Address is cached — only re-check deployment status
|
|
@@ -91,7 +107,7 @@ async function getPushAccountForCEA(ceaAddress, chain, rpcUrl) {
|
|
|
91
107
|
const viemChain = getViemChain(chain);
|
|
92
108
|
const client = (0, viem_1.createPublicClient)({
|
|
93
109
|
chain: viemChain,
|
|
94
|
-
transport: (
|
|
110
|
+
transport: ceaTransport(chain, rpcUrl),
|
|
95
111
|
});
|
|
96
112
|
const uea = await client.readContract({
|
|
97
113
|
abi: ceaFactory_evm_1.CEA_FACTORY_EVM,
|
|
@@ -118,7 +134,7 @@ async function isCEA(address, chain, rpcUrl) {
|
|
|
118
134
|
const viemChain = getViemChain(chain);
|
|
119
135
|
const client = (0, viem_1.createPublicClient)({
|
|
120
136
|
chain: viemChain,
|
|
121
|
-
transport: (
|
|
137
|
+
transport: ceaTransport(chain, rpcUrl),
|
|
122
138
|
});
|
|
123
139
|
return client.readContract({
|
|
124
140
|
abi: ceaFactory_evm_1.CEA_FACTORY_EVM,
|
|
@@ -75,7 +75,7 @@ export declare class PC20FactoryMismatchError extends PC20Error {
|
|
|
75
75
|
registryFactory?: string;
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
-
/** Token does not
|
|
78
|
+
/** Token does not expose usable ERC-20 metadata, or the metadata fails validation. */
|
|
79
79
|
export declare class InvalidPC20MetadataError extends PC20Error {
|
|
80
80
|
constructor(message: string, ctx?: PC20ErrorContext);
|
|
81
81
|
}
|
|
@@ -108,7 +108,7 @@ class PC20FactoryMismatchError extends PC20Error {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
exports.PC20FactoryMismatchError = PC20FactoryMismatchError;
|
|
111
|
-
/** Token does not
|
|
111
|
+
/** Token does not expose usable ERC-20 metadata, or the metadata fails validation. */
|
|
112
112
|
class InvalidPC20MetadataError extends PC20Error {
|
|
113
113
|
constructor(message, ctx = {}) {
|
|
114
114
|
super('PC20_INVALID_METADATA', message, ctx);
|
|
@@ -61,6 +61,8 @@ export type PC20GasQuote = {
|
|
|
61
61
|
protocolFee: bigint;
|
|
62
62
|
gasPrice: bigint;
|
|
63
63
|
chainNamespace: string;
|
|
64
|
+
/** Value encoded in the outbound request before UGPC applies its overhead. */
|
|
65
|
+
requestGasLimit: bigint;
|
|
64
66
|
gasLimitUsed: bigint;
|
|
65
67
|
isFirstExport: boolean;
|
|
66
68
|
/**
|
|
@@ -84,10 +86,11 @@ export type PC20GasQuote = {
|
|
|
84
86
|
* `pc20DeploymentGasOverhead` and the transfer strands on the destination
|
|
85
87
|
* with the source token already locked.
|
|
86
88
|
*
|
|
87
|
-
* The second is unrecoverable without operator intervention, so the
|
|
88
|
-
* always covers a deployment.
|
|
89
|
-
*
|
|
90
|
-
*
|
|
89
|
+
* The second is unrecoverable without operator intervention, so the execution
|
|
90
|
+
* gas limit always covers a deployment. The buffered limit must be re-quoted
|
|
91
|
+
* and written into the outbound request; funding a higher ceiling while leaving
|
|
92
|
+
* the request at the default limit still makes the relayer submit an
|
|
93
|
+
* under-gassed destination transaction.
|
|
91
94
|
*/
|
|
92
95
|
export declare function quotePC20Export(ctx: OrchestratorContext, pushPC20: `0x${string}`, destinationChain: CHAIN, gasLimit: bigint, opts: PC20ResolverOptions): Promise<PC20GasQuote>;
|
|
93
96
|
/**
|
|
@@ -103,6 +106,7 @@ export declare function queryPC20OutboundGasFee(ctx: OrchestratorContext, pushPC
|
|
|
103
106
|
gasFee: bigint;
|
|
104
107
|
protocolFee: bigint;
|
|
105
108
|
gasPrice: bigint;
|
|
109
|
+
requestGasLimit: bigint;
|
|
106
110
|
gasLimitUsed: bigint;
|
|
107
111
|
universalCoreAddress: `0x${string}`;
|
|
108
112
|
isFirstExport: boolean;
|
|
@@ -47,6 +47,7 @@ exports.queryPC20OutboundGasFee = queryPC20OutboundGasFee;
|
|
|
47
47
|
exports.resolveDestinationWrapper = resolveDestinationWrapper;
|
|
48
48
|
exports.validatePC20Export = validatePC20Export;
|
|
49
49
|
const viem_1 = require("viem");
|
|
50
|
+
const enums_1 = require("../../../constants/enums");
|
|
50
51
|
const abi_1 = require("../../../constants/abi");
|
|
51
52
|
const chain_1 = require("../../../constants/chain");
|
|
52
53
|
const context_1 = require("../context");
|
|
@@ -123,38 +124,55 @@ function assertMetadataFitsFactoryLimits(name, symbol) {
|
|
|
123
124
|
* `pc20DeploymentGasOverhead` and the transfer strands on the destination
|
|
124
125
|
* with the source token already locked.
|
|
125
126
|
*
|
|
126
|
-
* The second is unrecoverable without operator intervention, so the
|
|
127
|
-
* always covers a deployment.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
127
|
+
* The second is unrecoverable without operator intervention, so the execution
|
|
128
|
+
* gas limit always covers a deployment. The buffered limit must be re-quoted
|
|
129
|
+
* and written into the outbound request; funding a higher ceiling while leaving
|
|
130
|
+
* the request at the default limit still makes the relayer submit an
|
|
131
|
+
* under-gassed destination transaction.
|
|
130
132
|
*/
|
|
131
133
|
async function quotePC20Export(ctx, pushPC20, destinationChain, gasLimit, opts) {
|
|
132
134
|
const namespace = (0, chain_namespace_1.chainToNamespace)(destinationChain);
|
|
133
135
|
const core = await (0, resolver_1.getUniversalCoreAddress)(opts);
|
|
134
|
-
const
|
|
136
|
+
const readQuote = (limit) => ctx.pushClient.readContract({
|
|
135
137
|
address: core,
|
|
136
138
|
abi: abi_1.UNIVERSAL_CORE_EVM,
|
|
137
139
|
functionName: 'getPC20ExportGasAndFees',
|
|
138
|
-
args: [namespace,
|
|
140
|
+
args: [namespace, limit, pushPC20],
|
|
139
141
|
});
|
|
140
|
-
const [
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
const [baseQuote, overhead] = await Promise.all([
|
|
143
|
+
readQuote(gasLimit),
|
|
144
|
+
ctx.pushClient.readContract({
|
|
145
|
+
address: core,
|
|
146
|
+
abi: abi_1.UNIVERSAL_CORE_EVM,
|
|
147
|
+
functionName: 'pc20DeploymentGasOverhead',
|
|
148
|
+
args: [namespace],
|
|
149
|
+
}),
|
|
150
|
+
]);
|
|
151
|
+
const baseGasLimitUsed = baseQuote[5];
|
|
152
|
+
const isFirstExport = baseQuote[6];
|
|
153
|
+
// Feed the resolved first quote back as the explicit request limit. UGPC
|
|
154
|
+
// applies its deployment overhead to that value, giving one additional
|
|
155
|
+
// safety margin while keeping the fee quote and final destination limit in
|
|
156
|
+
// sync. Passing `gasLimitCeiling` here would make UGPC add the overhead yet
|
|
157
|
+
// again and over-provision the relayer transaction.
|
|
158
|
+
const gasLimitCeiling = baseGasLimitUsed + overhead;
|
|
159
|
+
const isEvmDestination = (0, chain_namespace_1.vmForChain)(destinationChain) === enums_1.VM.EVM;
|
|
160
|
+
const requestGasLimit = isEvmDestination ? baseGasLimitUsed : gasLimit;
|
|
161
|
+
// Solana has a separate rent/compute-budget requote path; preserve its base
|
|
162
|
+
// quote here so this EVM wrapper-deployment guard does not double-count it.
|
|
163
|
+
const bufferedQuote = isEvmDestination
|
|
164
|
+
? await readQuote(requestGasLimit)
|
|
165
|
+
: baseQuote;
|
|
166
|
+
const [gasToken, gasFee, protocolFee, gasPrice, chainNamespace, gasLimitUsed] = bufferedQuote;
|
|
167
|
+
(0, context_1.printLog)(ctx, `quotePC20Export — namespace=${chainNamespace}, baseGasLimit=${baseGasLimitUsed}, ` +
|
|
168
|
+
`isFirstExport=${isFirstExport}, overhead=${overhead}, executionGasLimit=${gasLimitUsed}`);
|
|
152
169
|
return {
|
|
153
170
|
gasToken,
|
|
154
171
|
gasFee,
|
|
155
172
|
protocolFee,
|
|
156
173
|
gasPrice,
|
|
157
174
|
chainNamespace,
|
|
175
|
+
requestGasLimit,
|
|
158
176
|
gasLimitUsed,
|
|
159
177
|
isFirstExport,
|
|
160
178
|
gasLimitCeiling,
|
|
@@ -180,6 +198,7 @@ async function queryPC20OutboundGasFee(ctx, pushPC20, gasLimit, destinationChain
|
|
|
180
198
|
gasFee: quote.gasFee,
|
|
181
199
|
protocolFee: quote.protocolFee,
|
|
182
200
|
gasPrice: quote.gasPrice,
|
|
201
|
+
requestGasLimit: quote.requestGasLimit,
|
|
183
202
|
gasLimitUsed: quote.gasLimitUsed,
|
|
184
203
|
universalCoreAddress,
|
|
185
204
|
isFirstExport: quote.isFirstExport,
|
|
@@ -36,12 +36,11 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import { type Hex } from 'viem';
|
|
38
38
|
import { CHAIN, PUSH_NETWORK, VM } from '../../../constants/enums';
|
|
39
|
-
/**
|
|
39
|
+
/** Standard ERC-20 metadata forwarded when exporting a Push-native PC20. */
|
|
40
40
|
export type PC20Metadata = {
|
|
41
41
|
name: string;
|
|
42
42
|
symbol: string;
|
|
43
43
|
decimals: number;
|
|
44
|
-
originAddress: `0x${string}`;
|
|
45
44
|
};
|
|
46
45
|
/** A confirmed wrapper deployment on one external chain. */
|
|
47
46
|
export type PC20DeploymentInfo = {
|
|
@@ -122,12 +121,11 @@ export declare function resolveWrapperToSource(chain: CHAIN, address: string, op
|
|
|
122
121
|
*/
|
|
123
122
|
export declare function verifyEvmWrapperIdentity(chain: CHAIN, address: string, pushAddress: `0x${string}`, opts: PC20ResolverOptions): Promise<void>;
|
|
124
123
|
/**
|
|
125
|
-
* Read and validate
|
|
124
|
+
* Read and validate standard ERC-20 metadata on a Push-native token.
|
|
126
125
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* "your token is broken".
|
|
126
|
+
* Every metadata-compatible ERC-20 born on Push Chain is eligible for PC20
|
|
127
|
+
* export. Synthetic PRC20s expose `CHAIN_NAMESPACE()` and are rejected even
|
|
128
|
+
* though they also implement the standard metadata functions.
|
|
131
129
|
*/
|
|
132
130
|
export declare function readPushPC20Metadata(pushAddress: `0x${string}`, opts: PC20ResolverOptions): Promise<PC20Metadata>;
|
|
133
131
|
/**
|
|
@@ -111,10 +111,9 @@ function clientFor(chain, opts) {
|
|
|
111
111
|
* dispatches them synchronously, which is what puts them in the same batch
|
|
112
112
|
* window — awaiting them one at a time would silently become N round trips.
|
|
113
113
|
*
|
|
114
|
-
* Failures are captured per call rather than thrown, because
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* individually via {@link unwrap}.
|
|
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}.
|
|
118
117
|
*/
|
|
119
118
|
async function batchRead(client, contracts) {
|
|
120
119
|
exports.__readCount.n += 1;
|
|
@@ -376,12 +375,11 @@ async function verifyEvmWrapperIdentity(chain, address, pushAddress, opts) {
|
|
|
376
375
|
// Push-native metadata
|
|
377
376
|
// ---------------------------------------------------------------------------
|
|
378
377
|
/**
|
|
379
|
-
* Read and validate
|
|
378
|
+
* Read and validate standard ERC-20 metadata on a Push-native token.
|
|
380
379
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
* "your token is broken".
|
|
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.
|
|
385
383
|
*/
|
|
386
384
|
async function readPushPC20Metadata(pushAddress, opts) {
|
|
387
385
|
const pushChain = (0, helpers_1.getPushChainForNetwork)(opts.network);
|
|
@@ -390,10 +388,26 @@ async function readPushPC20Metadata(pushAddress, opts) {
|
|
|
390
388
|
if (cached)
|
|
391
389
|
return cached;
|
|
392
390
|
const client = clientFor(pushChain, opts);
|
|
393
|
-
const [
|
|
391
|
+
const [metadataEntries, codeEntry] = await Promise.all([
|
|
394
392
|
batchRead(client, [
|
|
395
|
-
{ address: pushAddress, abi: abi_1.
|
|
396
|
-
|
|
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
|
+
]),
|
|
397
411
|
(async () => {
|
|
398
412
|
exports.__readCount.n += 1;
|
|
399
413
|
return { status: 'success', result: await client.getCode({ address: pushAddress }) };
|
|
@@ -406,22 +420,19 @@ async function readPushPC20Metadata(pushAddress, opts) {
|
|
|
406
420
|
address: pushAddress,
|
|
407
421
|
});
|
|
408
422
|
}
|
|
409
|
-
const
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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 });
|
|
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
|
+
});
|
|
418
430
|
}
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
throw new errors_1.InvalidPC20MetadataError(`pc20Metadata().originAddress (${originAddress}) does not match the token address.`, { chain: String(pushChain), address: pushAddress });
|
|
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 });
|
|
425
436
|
}
|
|
426
437
|
if (!name || !symbol) {
|
|
427
438
|
throw new errors_1.InvalidPC20MetadataError('PC20 name and symbol must be non-empty.', {
|
|
@@ -429,41 +440,21 @@ async function readPushPC20Metadata(pushAddress, opts) {
|
|
|
429
440
|
address: pushAddress,
|
|
430
441
|
});
|
|
431
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
|
+
}
|
|
432
450
|
const validated = {
|
|
433
451
|
name,
|
|
434
452
|
symbol,
|
|
435
|
-
decimals:
|
|
436
|
-
originAddress: (0, viem_1.getAddress)(originAddress),
|
|
453
|
+
decimals: normalizedDecimals,
|
|
437
454
|
};
|
|
438
455
|
metadataCache.set(key, validated);
|
|
439
456
|
return validated;
|
|
440
457
|
}
|
|
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
458
|
// ---------------------------------------------------------------------------
|
|
468
459
|
// Destination-side lookups
|
|
469
460
|
// ---------------------------------------------------------------------------
|
|
@@ -487,6 +487,7 @@ async function executeUoaToCea(ctx, params, executeFn) {
|
|
|
487
487
|
(0, context_1.printLog)(ctx, `executeUoaToCea — PAYLOAD-only: using native PRC-20 ${prc20Token} for chain ${targetChain} with zero burn amount`);
|
|
488
488
|
}
|
|
489
489
|
const gasLimitForQuery = params.gasLimit ?? BigInt(0);
|
|
490
|
+
let outboundGasLimit = gasLimitForQuery;
|
|
490
491
|
// --- 202: Gas estimation (spec-ordered before 203) ---
|
|
491
492
|
let gasFee = BigInt(0);
|
|
492
493
|
let protocolFee = BigInt(0);
|
|
@@ -499,9 +500,20 @@ async function executeUoaToCea(ctx, params, executeFn) {
|
|
|
499
500
|
// getPC20ExportGasAndFees accounts for the first-export deployment
|
|
500
501
|
// overhead, and quoting the wrong one strands the destination transfer.
|
|
501
502
|
const pc20Export = params._pc20;
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
503
|
+
const isPc20ExportQuote = pc20Export?.direction === 'export';
|
|
504
|
+
let result;
|
|
505
|
+
if (isPc20ExportQuote) {
|
|
506
|
+
const pc20Quote = await (0, export_1.queryPC20OutboundGasFee)(ctx, prc20Token, gasLimitForQuery, targetChain);
|
|
507
|
+
result = pc20Quote;
|
|
508
|
+
// PC20 first exports deploy a wrapper on the destination. The quote is
|
|
509
|
+
// re-issued with that deployment margin, and the relayer must receive
|
|
510
|
+
// the corresponding explicit request limit—not the caller's original
|
|
511
|
+
// zero/default.
|
|
512
|
+
outboundGasLimit = pc20Quote.requestGasLimit;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimitForQuery, targetChain);
|
|
516
|
+
}
|
|
505
517
|
gasFee = result.gasFee;
|
|
506
518
|
protocolFee = result.protocolFee;
|
|
507
519
|
gasToken = result.gasToken;
|
|
@@ -567,7 +579,7 @@ async function executeUoaToCea(ctx, params, executeFn) {
|
|
|
567
579
|
destinationUserData: ceaPayload,
|
|
568
580
|
})
|
|
569
581
|
: ceaPayload;
|
|
570
|
-
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(targetBytes, prc20Token, burnAmount,
|
|
582
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, outboundGasLimit, outboundPayload, ueaAddress, // revert recipient is the UEA
|
|
571
583
|
params.maxPCForGas ?? BigInt(0));
|
|
572
584
|
(0, context_1.printLog)(ctx, `executeUoaToCea — outbound request: ${JSON.stringify({
|
|
573
585
|
recipient: outboundReq.recipient,
|