@pushchain/core 6.0.27-alpha.4 → 6.0.27-alpha.6
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/AGW.md +25 -3
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/lib/agentic/error-messages.js +2 -0
- package/src/lib/agentic/errors.js +1 -1
- package/src/lib/agentic/execution/gas-budget.d.ts +19 -0
- package/src/lib/agentic/execution/gas-budget.js +54 -0
- package/src/lib/agentic/execution/outbound.js +11 -1
- package/src/lib/agentic/execution/svm-public.js +4 -1
- package/src/lib/agentic/execution/svm-quote.d.ts +2 -0
- package/src/lib/agentic/execution/svm-send.d.ts +2 -0
- package/src/lib/agentic/management/svm-public.js +3 -0
- package/src/lib/agentic/presets/index.js +1 -1
- package/src/lib/agentic/presets/routes.js +4 -5
- package/src/lib/agentic/public-rule.js +5 -0
- package/src/lib/agentic/runtime.d.ts +4 -0
- package/src/lib/agentic/runtime.js +23 -4
- package/src/lib/agentic/token-routes.d.ts +22 -0
- package/src/lib/agentic/token-routes.js +70 -0
- package/src/lib/constants/tokens.js +1 -1
- package/src/lib/orchestrator/internals/gas-calculator.js +11 -38
- package/src/lib/orchestrator/internals/gas-pool-quote.d.ts +34 -0
- package/src/lib/orchestrator/internals/gas-pool-quote.js +106 -0
- package/src/lib/orchestrator/internals/gas-usd-sizer.d.ts +5 -0
- package/src/lib/orchestrator/internals/gas-usd-sizer.js +29 -13
- package/src/lib/orchestrator/internals/svm-rent.d.ts +3 -1
- package/src/lib/orchestrator/internals/svm-rent.js +2 -1
package/AGW.md
CHANGED
|
@@ -136,7 +136,7 @@ Creation and rule-preparation errors include a zero-based `details.ruleIndex` wh
|
|
|
136
136
|
|
|
137
137
|
A DEX condition-limit failure reports `details.reason = 'RULE_CONSTRAINT_LIMIT'`, `actual`, `limit` and `callCount`. Conditions here are generated input checks (for example token, recipient and amount checks), rather than the number of visible function names. A stock two-token, single-router preset contains six allowed-call entries and twenty conditions. Appending the same call list again makes forty conditions. The SDK rejects this; it does not deduplicate, remove conditions or split authority automatically. Preserve per-chain rules and regenerate after edits rather than appending generated calls.
|
|
138
138
|
|
|
139
|
-
For the review screen, show the actual network (including testnet name), selected token addresses and limits. Label maxGasPerCall as a maximum PC gas budget, not a guaranteed fee or execution gas-unit count. maxTotal meters cumulative Push outbounds; it is not a lifetime destination-spend limit.
|
|
139
|
+
For the review screen, show the actual network (including testnet name), selected token addresses and limits. Label maxGasPerCall as a maximum PC gas budget, not a guaranteed fee or execution gas-unit count. maxTotal meters cumulative Push outbounds, including outbounds whose destination execution later fails; it is not a lifetime destination-spend limit. Destination pins and maxValue limit each call separately.
|
|
140
140
|
|
|
141
141
|
## Solana destinations
|
|
142
142
|
|
|
@@ -256,7 +256,7 @@ These are small, protocol-specific subsets chosen to fit the 32-call/32-pin ceil
|
|
|
256
256
|
| Arbitrum Sepolia | WETH / Circle USDC | Aave USDC |
|
|
257
257
|
| BSC testnet | WBNB / Venus USDT | Venus USDT, USDC |
|
|
258
258
|
|
|
259
|
-
Staking defaults are ETH/stETH/wstETH on Ethereum and the reviewed EigenLayer strategy's WETH on Sepolia. Testnet tokens with similar names can have different addresses; the returned addresses are authoritative. Defaults do not guarantee active reserves, liquidity or a Push PRC20 route. An unmodified preset cannot be granted when its assets lack matching PRC20 routes. The SDK preserves the selected tokens and caps and reports unavailable routes; it never removes assets to bypass this check. No runtime metadata/price fetch is added to get.
|
|
259
|
+
Staking defaults are ETH/stETH/wstETH on Ethereum and the reviewed EigenLayer strategy's WETH on Sepolia. Testnet tokens with similar names can have different addresses; the returned addresses are authoritative. Defaults do not guarantee active reserves, liquidity or a Push PRC20 route. An unmodified preset cannot be granted when its assets lack matching PRC20 routes. The SDK preserves the selected tokens and caps and reports unavailable routes; it never removes assets to bypass this check. On Donut, every default asset of DEXES Sepolia and LENDING Arbitrum Sepolia currently has a registered route; the other testnet combinations do not. No runtime metadata/price fetch is added to get.
|
|
260
260
|
|
|
261
261
|
### Funding and release-network checks
|
|
262
262
|
|
|
@@ -264,7 +264,7 @@ Missing PRC20 registry entries produce `BRIDGE_ASSET_UNAVAILABLE`, and readiness
|
|
|
264
264
|
|
|
265
265
|
The public preset API has no `funding` option. Remove it from existing integrations; supplying it now produces `INVALID_RULE` rather than silently changing permissions. Internal prefunded CEA test fixtures explicitly clear bridge assets to isolate protocol constraints. Those fixtures do not demonstrate that an unsupported preset route is executable.
|
|
266
266
|
|
|
267
|
-
Existing mappings distinguish token identity by chain and address, including address casing for EVM. WETH/stETH Sepolia mappings and string/address-word source metadata are supported; mainnet or other-chain WETH is never aliased to them.
|
|
267
|
+
Existing mappings distinguish token identity by chain and address, including address casing for EVM. WETH/stETH Sepolia mappings and string/address-word source metadata are supported; mainnet or other-chain WETH is never aliased to them. Exact routes enabled in the Push node registry are also resolved by address: Circle testnet USDC on Sepolia, Base Sepolia and Arbitrum Sepolia maps to its own PRC20, separate from the routed mock USDC. Grant preparation still verifies each PRC20 source on chain. Protocol mock LINK, Moonwell/Venus tokens, Base/Arbitrum WETH, BNB testnet WBNB and the EigenLayer WETH remain unavailable for bridge funding until matching routes exist.
|
|
268
268
|
|
|
269
269
|
### Default permission policy
|
|
270
270
|
|
|
@@ -353,3 +353,25 @@ Uniswap uses destination WETH/Circle USDC; Aave uses its faucet LINK. Their gene
|
|
|
353
353
|
The additional opt-in group `agw-presets-expanded` covers EigenLayer deposits on Sepolia; Uniswap swaps on Base/Arbitrum Sepolia; PancakeSwap plus Venus on BNB testnet; and Aave plus Moonwell on Base Sepolia. Use the same preflight/run commands with `--group agw-presets-expanded`. Each scenario funds a fresh wallet with at most 100 PC and checks any required destination-native PRC20 before transfer. Native-wrap cases use 10^12 raw native-token units. These are deposit/swap/redemption checks, not borrow/repay or every-market coverage.
|
|
354
354
|
|
|
355
355
|
AGW token resolution uses the destination chain for ordinary token objects: an explicit Base or Arbitrum ETH token must not select Sepolia's PRC20 merely because its symbol/address is identical. An explicit Push PRC20 reference must name the same source chain as the destination; a mismatch fails before signing.
|
|
356
|
+
|
|
357
|
+
### Creation UI: apps, tokens and preset availability
|
|
358
|
+
|
|
359
|
+
A rule requires at least one allowed function, even when tokens and limits are selected. A tokens-only rule returns `INVALID_RULE`, with `details.reason = 'ALLOWED_FUNCTION_REQUIRED'` and actionable `userMessage`. App selection can only be hidden if the application explicitly supplies the intended function permissions; the SDK does not invent transfer recipients or permissions.
|
|
360
|
+
|
|
361
|
+
`assets: []` remains valid for a universal rule with allowed calls. It grants no token transfers from Push; it never means all tokens. Allowed calls may still interact with destination-held assets under their configured constraints. There is no all-token wildcard. List each permitted token and cap explicitly (at most eight assets).
|
|
362
|
+
|
|
363
|
+
For presets, check `readiness.ready` before enabling Create. Display the per-chain issues and the affected token address. Empty selections explain that transfers from Push are disabled. `BRIDGE_ASSET_UNAVAILABLE` identifies an unverified SDK route, not proof that no deployment exists anywhere. Do not substitute a routed token solely because its symbol matches: the protocol must support that exact address, and swaps need compatible liquidity. A transfer-only helper is not included. An ERC20 transfer can be authorized explicitly with a `transfer(address,uint256)` allowed call pinned to a recipient and a per-transfer maximum; it also applies to that token already held in the wallet's destination account.
|
|
364
|
+
|
|
365
|
+
### Exact-preset acceptance and gas evidence
|
|
366
|
+
|
|
367
|
+
The opt-in `agw-presets-acceptance` group exercises all nine shipped testnet category/network combinations. It passes the returned `rules` unchanged to `create`, reads back every asset/cap/function/pin, and performs first-use and subsequent agent operations. Missing routes, protocols or exact PRC20 balances fail before funding; they do not count as passing acceptance. The opt-in `agw-presets-routed` group runs only the cases whose default assets are all routed (DEXES Sepolia and LENDING Arbitrum Sepolia); preflight checks readiness for the selected cases only. The earlier `agw-presets` and `agw-presets-expanded` protocol fixtures use prefunded destination assets and remain separate coverage.
|
|
368
|
+
|
|
369
|
+
Read-only compatibility checks are available with `node packages/core/scripts/check-agw-preset-compatibility.js /tmp/compatibility.json` after building core. The check verifies known routed source identities (registry and exact node routes), live Aave reserves, Moonwell/Venus underlyings, the reviewed EigenLayer strategy and pools at the preset routers' factories, including each DEX preset's own default pair. A matching symbol alone is insufficient. RPC and read errors are recorded as unknown checks, never as compatibility or as missing routes. No default assets were changed. `node packages/core/scripts/check-agw-route-registry.js /tmp/registry.json` compares the SDK's routes with the Push node's token registry (uregistry): it fails on a mismatched or SDK-only route and lists enabled node routes the SDK does not resolve.
|
|
370
|
+
|
|
371
|
+
`maxGasPerCall` is an owner-authorized ceiling on the PC attached to each outbound. The send-time `gasLimit` is a different field: the multiplier for the destination gas price. Omitted or zero uses the network base (500,000 on the EVM testnets, 960 on Solana devnet). Agent EVM sends without an explicit gasLimit instead use 2,000,000 on first use (the destination account is deployed), and 1,000,000 when they export a non-native token or make more than one destination call. A Sepolia Circle USDC export with approve and a Uniswap swap ran out of gas at the 500,000 base. An explicit value below the base is refused before quoting with `INVALID_RULE` and `details.minimum`. When omitted on Solana, the SDK raises the limit to cover finalization rent instead of using an EVM-style million-unit fallback. If a gas-price change leaves that budget short even after one re-quote, the send is refused before signing (`INCONSISTENT_READ`).
|
|
372
|
+
|
|
373
|
+
The attached budget is at least $1 of PC: 10 PC at the fixed $0.10 rate used for Push-native agents, and less or more for agents signing from another chain, which are priced through that origin's Donut stable pool. It is never less than the current Push pool cost of the destination gas plus 20%. BNB testnet gas is valued at that pool rate, because no BNB/USD feed exists (the BSC gateway feed is ETH/USD). The gateway refunds unused PC in the same transaction. After the destination executes, the node also refunds unused destination gas, `(gasLimit − gasUsed) × gasPrice`, to the wallet as PC in a later Push transaction; this was verified on Donut for EVM and Solana. On October 20 the gas swaps actually consumed 0.0008-2.37 PC per send. 0.1 PC can never cover the minimum budget. The 20 PC preset ceiling covered the Sepolia, Base Sepolia, Arbitrum Sepolia and Solana devnet runs; BNB testnet first sends exceed it while Donut's BNB gas price is high. There is no cumulative gas cap; the wallet's PC balance is the only total limit. `node packages/core/scripts/check-agw-gas-quotes.js /tmp/quotes.json` records current quotes for Push-native and Sepolia-origin agents without signing. Quotes change; they are not fixed fees or a validated default for custom rules.
|
|
374
|
+
|
|
375
|
+
The separate `agw-svm-agent-spl` group verifies the known devnet test harness's `receive_spl` instruction: wallet ATA pins, an amount ceiling, exact recipient credit and first/subsequent sends with omitted gas. It uses the existing counter program and does not certify a third-party Solana protocol. The gas evidence records Push transaction fees, attached quote budgets and destination fees separately. Run both new groups with the normal `__e2e__/ci/preflight.ts --group ...` and `__e2e__/ci/run.ts --group ...` commands.
|
|
376
|
+
|
|
377
|
+
The `agw-gas` group measures first-use and subsequent native-token agent sends on Sepolia, Base Sepolia, Arbitrum Sepolia and BNB testnet using the existing counter fixtures. Its route/gas coverage is separate from protocol preset acceptance.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
@pushchain/core@6.0.27-alpha.6 (2026-10-21)
|
|
2
|
+
|
|
3
|
+
- fix: route exact AGW tokens and size agent gas from the pool
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
@pushchain/core@6.0.27-alpha.5 (2026-10-20)
|
|
8
|
+
|
|
9
|
+
- fix: clarify agentic creation permissions and route guidance
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
1
13
|
@pushchain/core@6.0.27-alpha.4 (2026-10-20)
|
|
2
14
|
|
|
3
15
|
- fix: remove preset funding mode and preserve asset limits
|
package/package.json
CHANGED
|
@@ -43,6 +43,8 @@ function agenticUserMessage(code, diagnostic, details) {
|
|
|
43
43
|
}
|
|
44
44
|
if (code !== 'INVALID_RULE')
|
|
45
45
|
return MESSAGES[code];
|
|
46
|
+
if (details?.['reason'] === 'ALLOWED_FUNCTION_REQUIRED')
|
|
47
|
+
return 'Add at least one allowed function. Selecting tokens and limits alone does not authorize transfers.';
|
|
46
48
|
if (/validUntil|expiry|expired|future.*timestamp/i.test(diagnostic))
|
|
47
49
|
return 'Choose an expiry date in the future.';
|
|
48
50
|
if (/label.*(64|length|most)/i.test(diagnostic))
|
|
@@ -144,7 +144,7 @@ function tokenRouteUnavailable(cause, token, chainNamespace, assetIndex) {
|
|
|
144
144
|
const message = `This token cannot be sent from Push to ${network}. Choose a token with a supported Push route.`;
|
|
145
145
|
return new AgenticError(exports.AGENTIC_ERROR_CODE.TOKEN_ROUTE_UNAVAILABLE, message, {
|
|
146
146
|
userMessage: message,
|
|
147
|
-
hint: 'Choose a supported token on this network, or wait for a verified PRC20 route for this exact token address.
|
|
147
|
+
hint: 'Choose a supported token on this network, or wait for a verified PRC20 route for this exact token address. The selected app must also support that exact token address; matching symbols are not sufficient.',
|
|
148
148
|
details: {
|
|
149
149
|
token,
|
|
150
150
|
chainNamespace,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OrchestratorContext } from '../../orchestrator/internals/context';
|
|
2
|
+
/**
|
|
3
|
+
* PC gas budget (maxPCForGas) for an AGW outbound, excluding protocolFee.
|
|
4
|
+
* The gateway buys exactly `gasFee` of the gas token from the Donut WPC pool
|
|
5
|
+
* and reverts when the budget cannot. The shared USD sizer can attach less:
|
|
6
|
+
* its over-$10 overflow has no funds leg on this path, and its USD price can
|
|
7
|
+
* differ from the pool. Keep the larger of the sized budget and the pool cost.
|
|
8
|
+
*/
|
|
9
|
+
export declare function agwGasBudget(quote: {
|
|
10
|
+
protocolFee: bigint;
|
|
11
|
+
nativeValueForGas: bigint;
|
|
12
|
+
sizing?: {
|
|
13
|
+
category: string;
|
|
14
|
+
overflowNativePc: bigint;
|
|
15
|
+
};
|
|
16
|
+
}, poolPcIn?: bigint): bigint;
|
|
17
|
+
/** Refuses an explicit gasLimit below the chain base before quoting, where core
|
|
18
|
+
* would otherwise revert. Omitted or zero gas uses the base. */
|
|
19
|
+
export declare function assertGasLimitAtLeastBase(ctx: OrchestratorContext, chain: string, gasLimit: bigint): Promise<void>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.agwGasBudget = agwGasBudget;
|
|
4
|
+
exports.assertGasLimitAtLeastBase = assertGasLimitAtLeastBase;
|
|
5
|
+
const prc20_evm_1 = require("../../constants/abi/prc20.evm");
|
|
6
|
+
const prc20_metadata_1 = require("../contracts/prc20-metadata");
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
/** Headroom over the pool quote; the gateway refunds whatever the swap leaves. */
|
|
9
|
+
const POOL_HEADROOM_NUM = BigInt(12);
|
|
10
|
+
const POOL_HEADROOM_DEN = BigInt(10);
|
|
11
|
+
/**
|
|
12
|
+
* PC gas budget (maxPCForGas) for an AGW outbound, excluding protocolFee.
|
|
13
|
+
* The gateway buys exactly `gasFee` of the gas token from the Donut WPC pool
|
|
14
|
+
* and reverts when the budget cannot. The shared USD sizer can attach less:
|
|
15
|
+
* its over-$10 overflow has no funds leg on this path, and its USD price can
|
|
16
|
+
* differ from the pool. Keep the larger of the sized budget and the pool cost.
|
|
17
|
+
*/
|
|
18
|
+
function agwGasBudget(quote, poolPcIn) {
|
|
19
|
+
const sized = (quote.nativeValueForGas > quote.protocolFee
|
|
20
|
+
? quote.nativeValueForGas - quote.protocolFee
|
|
21
|
+
: BigInt(0)) +
|
|
22
|
+
(quote.sizing?.category === 'C'
|
|
23
|
+
? quote.sizing.overflowNativePc
|
|
24
|
+
: BigInt(0));
|
|
25
|
+
const pool = poolPcIn === undefined
|
|
26
|
+
? BigInt(0)
|
|
27
|
+
: (poolPcIn * POOL_HEADROOM_NUM + POOL_HEADROOM_DEN - BigInt(1)) /
|
|
28
|
+
POOL_HEADROOM_DEN;
|
|
29
|
+
return sized > pool ? sized : pool;
|
|
30
|
+
}
|
|
31
|
+
/** Refuses an explicit gasLimit below the chain base before quoting, where core
|
|
32
|
+
* would otherwise revert. Omitted or zero gas uses the base. */
|
|
33
|
+
async function assertGasLimitAtLeastBase(ctx, chain, gasLimit) {
|
|
34
|
+
if (gasLimit <= BigInt(0))
|
|
35
|
+
return;
|
|
36
|
+
let base;
|
|
37
|
+
try {
|
|
38
|
+
base = await ctx.pushClient.readContract({
|
|
39
|
+
address: prc20_metadata_1.CORE_ADDRESS,
|
|
40
|
+
abi: prc20_evm_1.UNIVERSAL_CORE_EVM,
|
|
41
|
+
functionName: 'baseGasLimitByChainNamespace',
|
|
42
|
+
args: [chain],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return; // The quote itself reports any real failure.
|
|
47
|
+
}
|
|
48
|
+
if (gasLimit < base)
|
|
49
|
+
throw new errors_1.AgenticError(errors_1.AGENTIC_ERROR_CODE.INVALID_RULE, `gasLimit ${gasLimit} is below the ${chain} minimum of ${base}; omit it or pass 0 to use the network default`, {
|
|
50
|
+
userMessage: 'The gas limit is below this network’s minimum. Leave it empty to use the network default.',
|
|
51
|
+
details: { field: 'gasLimit', minimum: base },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=gas-budget.js.map
|
|
@@ -14,6 +14,9 @@ const v6_1 = require("../contracts/v6");
|
|
|
14
14
|
const errors_1 = require("../errors");
|
|
15
15
|
/** Includes first-use CEA deployment; explicit caller gas always takes precedence. */
|
|
16
16
|
const FIRST_USE_EVM_GAS_LIMIT = BigInt(2_000_000);
|
|
17
|
+
/** Token exports and multi-call payloads can exceed the 500k network base: a
|
|
18
|
+
* Sepolia Circle USDC export with approve + Uniswap swap ran out of gas on it. */
|
|
19
|
+
const MULTI_STEP_EVM_GAS_LIMIT = BigInt(1_000_000);
|
|
17
20
|
/** URP MAX_ACTIONS_PER_REQUEST at v6. */
|
|
18
21
|
exports.MAX_DESTINATION_CALLS = 10;
|
|
19
22
|
const MULTICALL_TUPLE = [
|
|
@@ -146,8 +149,15 @@ async function composeOutbound(runtime, gen, wallet, params, opts) {
|
|
|
146
149
|
});
|
|
147
150
|
}
|
|
148
151
|
}
|
|
152
|
+
const multiStep = calls.length > 1 ||
|
|
153
|
+
(amount > BigInt(0) &&
|
|
154
|
+
(0, viem_1.getAddress)(token) !== (0, viem_1.getAddress)(runtime.resolvePrc20(undefined, chain)));
|
|
149
155
|
const quote = await runtime.quoteOutbound(token, params.gasLimit ??
|
|
150
|
-
(resolved.isDeployed
|
|
156
|
+
(!resolved.isDeployed
|
|
157
|
+
? FIRST_USE_EVM_GAS_LIMIT
|
|
158
|
+
: multiStep
|
|
159
|
+
? MULTI_STEP_EVM_GAS_LIMIT
|
|
160
|
+
: BigInt(0)), chain);
|
|
151
161
|
if (quote.nativeValueForGas <= BigInt(0)) {
|
|
152
162
|
throw new errors_1.AgenticError(errors_1.AGENTIC_ERROR_CODE.INVALID_RULE, 'outbound gas quote returned zero; URP rejects an uncapped gas swap');
|
|
153
163
|
}
|
|
@@ -50,7 +50,10 @@ async function sendPublicSvm(runtime, actx, p, options) {
|
|
|
50
50
|
let rulesId, data, instruction, cea;
|
|
51
51
|
let destinationTransfer;
|
|
52
52
|
let transferValue = BigInt(0);
|
|
53
|
-
|
|
53
|
+
// SVM gateway gasLimit is a lamport-budget conversion input, not EVM gas
|
|
54
|
+
// units. Let the live route quote and finalize-rent floor size an omitted
|
|
55
|
+
// limit; copying an EVM-style million-unit fallback can overquote 100 PC.
|
|
56
|
+
const gasLimit = p.gasLimit ?? BigInt(0);
|
|
54
57
|
if (actx.door === 'agent') {
|
|
55
58
|
const active = await (0, rules_1.selectRuleForSend)(snap, gen, wallet, actx.signerPushAccount, to.chain);
|
|
56
59
|
const stored = await (0, svm_1.readSvmConfig)(snap, gen, wallet, active, runtime.pushChainNamespace);
|
|
@@ -52,6 +52,9 @@ async function preparePublicSvmRule(runtime, snap, wallet, rule) {
|
|
|
52
52
|
throw (0, svm_terms_1.svmInvalid)('unknown Solana chain');
|
|
53
53
|
if (!Array.isArray(rule.assets) || rule.assets.length > 8)
|
|
54
54
|
throw (0, svm_terms_1.svmInvalid)('assets must contain at most eight entries');
|
|
55
|
+
// URP compares the attached PC to this ceiling, so zero refuses every send.
|
|
56
|
+
if (rule.maxGasPerCall === BigInt(0))
|
|
57
|
+
throw (0, svm_terms_1.svmInvalid)('maxGasPerCall must be positive');
|
|
55
58
|
const raw = 'format' in rule && rule.format === 'decoded';
|
|
56
59
|
const outputTokens = 'outputTokens' in rule ? rule.outputTokens : undefined;
|
|
57
60
|
if (outputTokens !== undefined && !Array.isArray(outputTokens))
|
|
@@ -313,7 +313,7 @@ function buildPresetApi(catalog) {
|
|
|
313
313
|
if (!selectedAssets.length)
|
|
314
314
|
issues.push({
|
|
315
315
|
code: 'ASSETS_REQUIRED',
|
|
316
|
-
message: 'Choose tokens and
|
|
316
|
+
message: 'No tokens selected: transfers from Push are disabled, not unrestricted. Choose explicit tokens and limits, then regenerate this preset.',
|
|
317
317
|
});
|
|
318
318
|
if (chain.swapRouters?.length && selectedAssets.length < 2)
|
|
319
319
|
issues.push({
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.presetRoutes = presetRoutes;
|
|
4
|
-
const viem_1 = require("viem");
|
|
5
4
|
const enums_1 = require("../../constants/enums");
|
|
6
5
|
const helpers_1 = require("../../orchestrator/internals/helpers");
|
|
7
|
-
const
|
|
6
|
+
const token_routes_1 = require("../token-routes");
|
|
8
7
|
const deployments_1 = require("../deployments");
|
|
9
8
|
const operation_limits_1 = require("./operation-limits");
|
|
10
9
|
/** Offline registry checks only. Grant preparation still verifies source metadata
|
|
@@ -29,10 +28,10 @@ function presetRoutes(chain, assets, network) {
|
|
|
29
28
|
try {
|
|
30
29
|
if (!supported)
|
|
31
30
|
throw new Error(`No supported gateway route to ${chain}`);
|
|
32
|
-
const prc20 = (0,
|
|
31
|
+
const prc20 = (0, token_routes_1.resolveAgenticPrc20)({ chain, address: token }, network);
|
|
33
32
|
return {
|
|
34
33
|
token,
|
|
35
|
-
prc20:
|
|
34
|
+
prc20: prc20.address,
|
|
36
35
|
status: 'registered',
|
|
37
36
|
};
|
|
38
37
|
}
|
|
@@ -40,7 +39,7 @@ function presetRoutes(chain, assets, network) {
|
|
|
40
39
|
issues.push({
|
|
41
40
|
code: 'BRIDGE_ASSET_UNAVAILABLE',
|
|
42
41
|
token,
|
|
43
|
-
message: `No SDK PRC20 mapping for ${chain}:${token} on ${network}.
|
|
42
|
+
message: `No SDK PRC20 mapping for ${chain}:${token} on ${network}. This preset cannot be created with this token until its Push route is verified. Choose a routed token only if the selected app supports that exact token address.`,
|
|
44
43
|
});
|
|
45
44
|
return { token, status: 'unavailable' };
|
|
46
45
|
}
|
|
@@ -117,6 +117,11 @@ async function normalizePublicRule(input, wallet, runtime) {
|
|
|
117
117
|
r.validUntil <= runtime.nowSeconds() ||
|
|
118
118
|
r.validUntil >= 2 ** 48)
|
|
119
119
|
throw invalid('validUntil must be a future uint48 timestamp');
|
|
120
|
+
if (r.allowedCalls === undefined ||
|
|
121
|
+
(Array.isArray(r.allowedCalls) && !r.allowedCalls.length))
|
|
122
|
+
throw new errors_1.AgenticError(errors_1.AGENTIC_ERROR_CODE.INVALID_RULE, 'Add at least one allowed function. Selecting tokens and limits alone does not authorize transfers.', {
|
|
123
|
+
details: { reason: 'ALLOWED_FUNCTION_REQUIRED', field: 'allowedCalls' },
|
|
124
|
+
});
|
|
120
125
|
if (!Array.isArray(r.allowedCalls) ||
|
|
121
126
|
r.allowedCalls.length < 1 ||
|
|
122
127
|
r.allowedCalls.length > 32)
|
|
@@ -44,8 +44,12 @@ export interface AgenticRuntime {
|
|
|
44
44
|
burnAmount: bigint;
|
|
45
45
|
}): Promise<{
|
|
46
46
|
protocolFee: bigint;
|
|
47
|
+
/** PC gas budget sent as maxPCForGas; protocolFee is attached separately. */
|
|
47
48
|
nativeValueForGas: bigint;
|
|
48
49
|
gasLimitUsed: bigint;
|
|
50
|
+
/** Diagnostics: gas-token fee and the pool's current PC cost for it. */
|
|
51
|
+
gasFee?: bigint;
|
|
52
|
+
poolPcIn?: bigint;
|
|
49
53
|
}>;
|
|
50
54
|
resolveCEA(account: Address, chain: CHAIN): Promise<{
|
|
51
55
|
cea: Address;
|
|
@@ -12,6 +12,9 @@ const gas_calculator_1 = require("../orchestrator/internals/gas-calculator");
|
|
|
12
12
|
const helpers_1 = require("../orchestrator/internals/helpers");
|
|
13
13
|
const chain_1 = require("./chain");
|
|
14
14
|
const prc20_address_1 = require("../universal/prc20-address");
|
|
15
|
+
const token_routes_1 = require("./token-routes");
|
|
16
|
+
const gas_budget_1 = require("./execution/gas-budget");
|
|
17
|
+
const gas_pool_quote_1 = require("../orchestrator/internals/gas-pool-quote");
|
|
15
18
|
const reader_1 = require("./contracts/reader");
|
|
16
19
|
const svm_metadata_1 = require("./management/svm-metadata");
|
|
17
20
|
const chain_2 = require("../constants/chain");
|
|
@@ -63,23 +66,39 @@ function createAgenticRuntime(orchestrator, isReadOnly) {
|
|
|
63
66
|
signerBalance: () => ctx.pushClient.getBalance((0, uea_manager_1.computeUEAOffchain)(ctx)),
|
|
64
67
|
getGasPrice: () => ctx.pushClient.getGasPrice(),
|
|
65
68
|
quoteOutbound: async (prc20, gasLimit, chain, svm) => {
|
|
69
|
+
await (0, gas_budget_1.assertGasLimitAtLeastBase)(ctx, chain, gasLimit);
|
|
66
70
|
let q = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20, gasLimit, chain);
|
|
67
71
|
if (svm) {
|
|
68
|
-
|
|
72
|
+
let minimum = BigInt(0);
|
|
73
|
+
const finalize = (quote) => (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
69
74
|
ctx,
|
|
70
75
|
ueaAddress: svm.wallet,
|
|
71
76
|
targetChain: chain,
|
|
72
77
|
prc20Token: prc20,
|
|
73
|
-
quote
|
|
78
|
+
quote,
|
|
74
79
|
splMintBase58: svm.splMintBase58,
|
|
75
80
|
burnAmount: svm.burnAmount,
|
|
76
81
|
pathTag: 'AGW_SVM',
|
|
82
|
+
onMinimum: (m) => {
|
|
83
|
+
minimum = m;
|
|
84
|
+
},
|
|
77
85
|
});
|
|
86
|
+
q = await finalize(q);
|
|
87
|
+
// A gas-price change between quote and re-quote can leave the bump short.
|
|
88
|
+
if (q.gasFee < minimum)
|
|
89
|
+
q = await finalize(q);
|
|
90
|
+
if (q.gasFee < minimum)
|
|
91
|
+
throw new errors_2.AgenticError(errors_2.AGENTIC_ERROR_CODE.INCONSISTENT_READ, `Solana finalize budget ${q.gasFee} is below the required ${minimum} lamports after re-quoting`, {
|
|
92
|
+
userMessage: 'Network gas prices changed while preparing this transaction. Try again.',
|
|
93
|
+
});
|
|
78
94
|
}
|
|
95
|
+
const poolPcIn = await (0, gas_pool_quote_1.quoteGasPoolCost)(ctx, q.universalCoreAddress, q.gasToken, q.gasFee);
|
|
79
96
|
return {
|
|
80
97
|
protocolFee: q.protocolFee,
|
|
81
|
-
nativeValueForGas: q
|
|
98
|
+
nativeValueForGas: (0, gas_budget_1.agwGasBudget)(q, poolPcIn),
|
|
82
99
|
gasLimitUsed: q.gasLimitUsed,
|
|
100
|
+
gasFee: q.gasFee,
|
|
101
|
+
poolPcIn,
|
|
83
102
|
};
|
|
84
103
|
},
|
|
85
104
|
resolveCEA: async (account, chain) => {
|
|
@@ -96,7 +115,7 @@ function createAgenticRuntime(orchestrator, isReadOnly) {
|
|
|
96
115
|
throw new errors_2.AgenticError(errors_2.AGENTIC_ERROR_CODE.ASSET_CHAIN_MISMATCH, 'Push token source chain must match the destination chain');
|
|
97
116
|
return (0, viem_1.getAddress)((0, prc20_address_1.getPRC20Address)(token, { network }).address);
|
|
98
117
|
}
|
|
99
|
-
return (0,
|
|
118
|
+
return (0, token_routes_1.resolveAgenticPrc20)({ chain, address: typeof token === 'string' ? token : token.address }, network).address;
|
|
100
119
|
},
|
|
101
120
|
nowSeconds: () => Math.floor(Date.now() / 1000),
|
|
102
121
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
|
+
import { PUSH_NETWORK } from '../constants/enums';
|
|
3
|
+
export interface AgenticTokenRoute {
|
|
4
|
+
/** Exact destination-chain token address. */
|
|
5
|
+
source: Address;
|
|
6
|
+
/** Push PRC20 the node registry maps that token to. */
|
|
7
|
+
prc20: Address;
|
|
8
|
+
symbol: string;
|
|
9
|
+
decimals: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function exactAgenticRoutes(chain: string, network: PUSH_NETWORK): readonly AgenticTokenRoute[];
|
|
12
|
+
/** Match by chain AND exact address; never by symbol. */
|
|
13
|
+
export declare function exactAgenticRoute(chain: string, source: string, network: PUSH_NETWORK): AgenticTokenRoute | undefined;
|
|
14
|
+
/** AGW source-token resolution: exact node routes first, then the shared registry. */
|
|
15
|
+
export declare function resolveAgenticPrc20(token: {
|
|
16
|
+
chain: string;
|
|
17
|
+
address: string;
|
|
18
|
+
}, network: PUSH_NETWORK): {
|
|
19
|
+
address: Address;
|
|
20
|
+
symbol: string;
|
|
21
|
+
decimals: number;
|
|
22
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exactAgenticRoutes = exactAgenticRoutes;
|
|
4
|
+
exports.exactAgenticRoute = exactAgenticRoute;
|
|
5
|
+
exports.resolveAgenticPrc20 = resolveAgenticPrc20;
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const enums_1 = require("../constants/enums");
|
|
8
|
+
const prc20_address_1 = require("../universal/prc20-address");
|
|
9
|
+
/** Exact routes enabled in the node's uregistry that the shared symbol-keyed
|
|
10
|
+
* registry cannot hold (a second USDC on one chain). Snapshot: Donut uregistry
|
|
11
|
+
* height 24,457,377; SOURCE_* metadata, decimals and outbound quotes checked at
|
|
12
|
+
* block 24,457,398. Grant preparation still verifies the source on chain.
|
|
13
|
+
*/
|
|
14
|
+
const DONUT_EXACT_ROUTES = {
|
|
15
|
+
'eip155:11155111': [
|
|
16
|
+
{
|
|
17
|
+
source: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
|
|
18
|
+
prc20: '0x387b9C8Db60E74999aAAC5A2b7825b400F12d68E',
|
|
19
|
+
symbol: 'USDC',
|
|
20
|
+
decimals: 6,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
'eip155:84532': [
|
|
24
|
+
{
|
|
25
|
+
source: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
26
|
+
prc20: '0x84B62e44F667F692F7739Ca6040cD17DA02068A8',
|
|
27
|
+
symbol: 'USDC',
|
|
28
|
+
decimals: 6,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
'eip155:421614': [
|
|
32
|
+
{
|
|
33
|
+
source: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
|
|
34
|
+
prc20: '0xa261A10e94aE4bA88EE8c5845CbE7266bD679DD6',
|
|
35
|
+
symbol: 'USDC',
|
|
36
|
+
decimals: 6,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
const EXACT_ROUTES = {
|
|
41
|
+
[enums_1.PUSH_NETWORK.TESTNET_DONUT]: DONUT_EXACT_ROUTES,
|
|
42
|
+
[enums_1.PUSH_NETWORK.TESTNET]: DONUT_EXACT_ROUTES,
|
|
43
|
+
};
|
|
44
|
+
function exactAgenticRoutes(chain, network) {
|
|
45
|
+
return EXACT_ROUTES[network]?.[chain] ?? [];
|
|
46
|
+
}
|
|
47
|
+
/** Match by chain AND exact address; never by symbol. */
|
|
48
|
+
function exactAgenticRoute(chain, source, network) {
|
|
49
|
+
if (!(0, viem_1.isAddress)(source, { strict: false }))
|
|
50
|
+
return undefined;
|
|
51
|
+
const wanted = (0, viem_1.getAddress)(source);
|
|
52
|
+
return exactAgenticRoutes(chain, network).find((route) => (0, viem_1.getAddress)(route.source) === wanted);
|
|
53
|
+
}
|
|
54
|
+
/** AGW source-token resolution: exact node routes first, then the shared registry. */
|
|
55
|
+
function resolveAgenticPrc20(token, network) {
|
|
56
|
+
const exact = exactAgenticRoute(token.chain, token.address, network);
|
|
57
|
+
if (exact)
|
|
58
|
+
return {
|
|
59
|
+
address: (0, viem_1.getAddress)(exact.prc20),
|
|
60
|
+
symbol: exact.symbol,
|
|
61
|
+
decimals: exact.decimals,
|
|
62
|
+
};
|
|
63
|
+
const mapped = (0, prc20_address_1.getPRC20Address)(token, { network });
|
|
64
|
+
return {
|
|
65
|
+
address: (0, viem_1.getAddress)(mapped.address),
|
|
66
|
+
symbol: mapped.symbol,
|
|
67
|
+
decimals: mapped.decimals,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=token-routes.js.map
|
|
@@ -25,6 +25,7 @@ const utils_1 = require("../../utils");
|
|
|
25
25
|
const svm_client_1 = require("../../vm-client/svm-client");
|
|
26
26
|
const context_1 = require("./context");
|
|
27
27
|
const helpers_1 = require("./helpers");
|
|
28
|
+
const gas_pool_quote_1 = require("./gas-pool-quote");
|
|
28
29
|
const gas_usd_sizer_1 = require("./gas-usd-sizer");
|
|
29
30
|
/**
|
|
30
31
|
* Legacy fallback buffer used ONLY when the caller does not supply a
|
|
@@ -64,36 +65,6 @@ const UNISWAP_V3_FACTORY_ABI = [
|
|
|
64
65
|
stateMutability: 'view',
|
|
65
66
|
},
|
|
66
67
|
];
|
|
67
|
-
const UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI = [
|
|
68
|
-
{
|
|
69
|
-
type: 'function',
|
|
70
|
-
name: 'quoteExactOutputSingle',
|
|
71
|
-
inputs: [
|
|
72
|
-
{
|
|
73
|
-
type: 'tuple',
|
|
74
|
-
components: [
|
|
75
|
-
{ name: 'tokenIn', type: 'address' },
|
|
76
|
-
{ name: 'tokenOut', type: 'address' },
|
|
77
|
-
{ name: 'amount', type: 'uint256' },
|
|
78
|
-
{ name: 'fee', type: 'uint24' },
|
|
79
|
-
{ name: 'sqrtPriceLimitX96', type: 'uint160' },
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
outputs: [
|
|
84
|
-
{ name: 'amountIn', type: 'uint256' },
|
|
85
|
-
{ name: 'sqrtPriceX96After', type: 'uint160' },
|
|
86
|
-
{ name: 'initializedTicksCrossed', type: 'uint32' },
|
|
87
|
-
{ name: 'gasEstimate', type: 'uint256' },
|
|
88
|
-
],
|
|
89
|
-
stateMutability: 'view',
|
|
90
|
-
},
|
|
91
|
-
];
|
|
92
|
-
const UNIVERSAL_CORE_SWAP_HELPERS_ABI = [
|
|
93
|
-
{ type: 'function', name: 'WPC', inputs: [], outputs: [{ name: '', type: 'address', internalType: 'address' }], stateMutability: 'view' },
|
|
94
|
-
{ type: 'function', name: 'uniswapV3Factory', inputs: [], outputs: [{ name: '', type: 'address', internalType: 'address' }], stateMutability: 'view' },
|
|
95
|
-
{ type: 'function', name: 'defaultFeeTier', inputs: [{ name: '', type: 'address', internalType: 'address' }], outputs: [{ name: '', type: 'uint24', internalType: 'uint24' }], stateMutability: 'view' },
|
|
96
|
-
];
|
|
97
68
|
const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
|
|
98
69
|
// ============================================================================
|
|
99
70
|
// ERC-20 Allowance
|
|
@@ -211,6 +182,7 @@ async function queryOutboundGasFee(ctx, prc20Token, gasLimit, destinationChain)
|
|
|
211
182
|
gasFee,
|
|
212
183
|
originChain: ctx.universalSigner.account.chain,
|
|
213
184
|
destinationChain,
|
|
185
|
+
pool: { universalCore: universalCoreAddress, gasToken },
|
|
214
186
|
});
|
|
215
187
|
// All categories — A, B, and C — return the sizer's calibrated gas leg.
|
|
216
188
|
// R3 consumers additionally bump `nativeValueForGas` by `overflowNativePc`
|
|
@@ -259,19 +231,19 @@ async function estimateNativeValueForSwap(ctx, universalCoreAddress, gasToken, g
|
|
|
259
231
|
const [wpcAddress, factoryAddress, feeTier] = await Promise.all([
|
|
260
232
|
ctx.pushClient.readContract({
|
|
261
233
|
address: universalCoreAddress,
|
|
262
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
234
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
263
235
|
functionName: 'WPC',
|
|
264
236
|
args: [],
|
|
265
237
|
}),
|
|
266
238
|
ctx.pushClient.readContract({
|
|
267
239
|
address: universalCoreAddress,
|
|
268
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
240
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
269
241
|
functionName: 'uniswapV3Factory',
|
|
270
242
|
args: [],
|
|
271
243
|
}),
|
|
272
244
|
ctx.pushClient.readContract({
|
|
273
245
|
address: universalCoreAddress,
|
|
274
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
246
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
275
247
|
functionName: 'defaultFeeTier',
|
|
276
248
|
args: [gasToken],
|
|
277
249
|
}),
|
|
@@ -286,7 +258,7 @@ async function estimateNativeValueForSwap(ctx, universalCoreAddress, gasToken, g
|
|
|
286
258
|
try {
|
|
287
259
|
const quote = await ctx.pushClient.readContract({
|
|
288
260
|
address: quoterAddress,
|
|
289
|
-
abi: UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI,
|
|
261
|
+
abi: gas_pool_quote_1.UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI,
|
|
290
262
|
functionName: 'quoteExactOutputSingle',
|
|
291
263
|
args: [
|
|
292
264
|
{
|
|
@@ -437,6 +409,7 @@ async function queryRescueGasFee(ctx, prc20Token, destinationChain) {
|
|
|
437
409
|
gasFee,
|
|
438
410
|
originChain: ctx.universalSigner.account.chain,
|
|
439
411
|
destinationChain,
|
|
412
|
+
pool: { universalCore: universalCoreAddress, gasToken },
|
|
440
413
|
});
|
|
441
414
|
nativeValueForGas = sizing.gasLegNativePc;
|
|
442
415
|
(0, context_1.printLog)(ctx, `queryRescueGasFee — sizer: category=${sizing.category}, gasUsd=${sizing.gasUsd}, nativeValueForGas=${nativeValueForGas}`);
|
|
@@ -575,13 +548,13 @@ async function estimateDepositFromLockedNative(ctx, nativeAmountForLocker, prc20
|
|
|
575
548
|
const [wpcAddress, feeTier] = await Promise.all([
|
|
576
549
|
ctx.pushClient.readContract({
|
|
577
550
|
address: universalCoreAddress,
|
|
578
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
551
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
579
552
|
functionName: 'WPC',
|
|
580
553
|
args: [],
|
|
581
554
|
}),
|
|
582
555
|
ctx.pushClient.readContract({
|
|
583
556
|
address: universalCoreAddress,
|
|
584
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
557
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
585
558
|
functionName: 'defaultFeeTier',
|
|
586
559
|
args: [prc20Token],
|
|
587
560
|
}),
|
|
@@ -664,13 +637,13 @@ async function estimateNativeForDesiredDeposit(ctx, desiredWpc, prc20Token) {
|
|
|
664
637
|
const [wpcAddress, feeTier] = await Promise.all([
|
|
665
638
|
ctx.pushClient.readContract({
|
|
666
639
|
address: universalCoreAddress,
|
|
667
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
640
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
668
641
|
functionName: 'WPC',
|
|
669
642
|
args: [],
|
|
670
643
|
}),
|
|
671
644
|
ctx.pushClient.readContract({
|
|
672
645
|
address: universalCoreAddress,
|
|
673
|
-
abi: UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
646
|
+
abi: gas_pool_quote_1.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
674
647
|
functionName: 'defaultFeeTier',
|
|
675
648
|
args: [prc20Token],
|
|
676
649
|
}),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { OrchestratorContext } from './context';
|
|
2
|
+
export declare const UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI: {
|
|
3
|
+
type: "function";
|
|
4
|
+
name: string;
|
|
5
|
+
inputs: {
|
|
6
|
+
type: string;
|
|
7
|
+
components: {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
}[];
|
|
11
|
+
}[];
|
|
12
|
+
outputs: {
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
}[];
|
|
16
|
+
stateMutability: string;
|
|
17
|
+
}[];
|
|
18
|
+
export declare const UNIVERSAL_CORE_SWAP_HELPERS_ABI: {
|
|
19
|
+
type: "function";
|
|
20
|
+
name: string;
|
|
21
|
+
inputs: {
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
internalType: string;
|
|
25
|
+
}[];
|
|
26
|
+
outputs: {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
internalType: string;
|
|
30
|
+
}[];
|
|
31
|
+
stateMutability: string;
|
|
32
|
+
}[];
|
|
33
|
+
/** Exact PC (WPC wei) the gas swap needs now; undefined when it cannot be quoted. */
|
|
34
|
+
export declare function quoteGasPoolCost(ctx: OrchestratorContext, universalCore: `0x${string}`, gasToken: `0x${string}`, gasFee: bigint): Promise<bigint | undefined>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UNIVERSAL_CORE_SWAP_HELPERS_ABI = exports.UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI = void 0;
|
|
4
|
+
exports.quoteGasPoolCost = quoteGasPoolCost;
|
|
5
|
+
/**
|
|
6
|
+
* Live Push-pool cost of an outbound gas fee. The gateway's swapAndBurnGas
|
|
7
|
+
* buys exactly `gasFee` of the gas token from the WPC pool (exact output), so
|
|
8
|
+
* this quote is what the swap will actually need at the current pool state.
|
|
9
|
+
*/
|
|
10
|
+
const chain_1 = require("../../constants/chain");
|
|
11
|
+
const context_1 = require("./context");
|
|
12
|
+
const helpers_1 = require("./helpers");
|
|
13
|
+
exports.UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI = [
|
|
14
|
+
{
|
|
15
|
+
type: 'function',
|
|
16
|
+
name: 'quoteExactOutputSingle',
|
|
17
|
+
inputs: [
|
|
18
|
+
{
|
|
19
|
+
type: 'tuple',
|
|
20
|
+
components: [
|
|
21
|
+
{ name: 'tokenIn', type: 'address' },
|
|
22
|
+
{ name: 'tokenOut', type: 'address' },
|
|
23
|
+
{ name: 'amount', type: 'uint256' },
|
|
24
|
+
{ name: 'fee', type: 'uint24' },
|
|
25
|
+
{ name: 'sqrtPriceLimitX96', type: 'uint160' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
outputs: [
|
|
30
|
+
{ name: 'amountIn', type: 'uint256' },
|
|
31
|
+
{ name: 'sqrtPriceX96After', type: 'uint160' },
|
|
32
|
+
{ name: 'initializedTicksCrossed', type: 'uint32' },
|
|
33
|
+
{ name: 'gasEstimate', type: 'uint256' },
|
|
34
|
+
],
|
|
35
|
+
stateMutability: 'view',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
exports.UNIVERSAL_CORE_SWAP_HELPERS_ABI = [
|
|
39
|
+
{
|
|
40
|
+
type: 'function',
|
|
41
|
+
name: 'WPC',
|
|
42
|
+
inputs: [],
|
|
43
|
+
outputs: [{ name: '', type: 'address', internalType: 'address' }],
|
|
44
|
+
stateMutability: 'view',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'function',
|
|
48
|
+
name: 'uniswapV3Factory',
|
|
49
|
+
inputs: [],
|
|
50
|
+
outputs: [{ name: '', type: 'address', internalType: 'address' }],
|
|
51
|
+
stateMutability: 'view',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: 'function',
|
|
55
|
+
name: 'defaultFeeTier',
|
|
56
|
+
inputs: [{ name: '', type: 'address', internalType: 'address' }],
|
|
57
|
+
outputs: [{ name: '', type: 'uint24', internalType: 'uint24' }],
|
|
58
|
+
stateMutability: 'view',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
/** Exact PC (WPC wei) the gas swap needs now; undefined when it cannot be quoted. */
|
|
62
|
+
async function quoteGasPoolCost(ctx, universalCore, gasToken, gasFee) {
|
|
63
|
+
if (gasFee <= BigInt(0))
|
|
64
|
+
return undefined;
|
|
65
|
+
const quoter = chain_1.CHAIN_INFO[(0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork)]?.dex?.uniV3QuoterV2;
|
|
66
|
+
if (!quoter || quoter.startsWith('0xTBD'))
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
const [wpc, fee] = await Promise.all([
|
|
70
|
+
ctx.pushClient.readContract({
|
|
71
|
+
address: universalCore,
|
|
72
|
+
abi: exports.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
73
|
+
functionName: 'WPC',
|
|
74
|
+
args: [],
|
|
75
|
+
}),
|
|
76
|
+
ctx.pushClient.readContract({
|
|
77
|
+
address: universalCore,
|
|
78
|
+
abi: exports.UNIVERSAL_CORE_SWAP_HELPERS_ABI,
|
|
79
|
+
functionName: 'defaultFeeTier',
|
|
80
|
+
args: [gasToken],
|
|
81
|
+
}),
|
|
82
|
+
]);
|
|
83
|
+
if (!fee)
|
|
84
|
+
return undefined;
|
|
85
|
+
const quote = await ctx.pushClient.readContract({
|
|
86
|
+
address: quoter,
|
|
87
|
+
abi: exports.UNISWAP_V3_QUOTER_EXACT_OUTPUT_ABI,
|
|
88
|
+
functionName: 'quoteExactOutputSingle',
|
|
89
|
+
args: [
|
|
90
|
+
{
|
|
91
|
+
tokenIn: wpc,
|
|
92
|
+
tokenOut: gasToken,
|
|
93
|
+
amount: gasFee,
|
|
94
|
+
fee,
|
|
95
|
+
sqrtPriceLimitX96: BigInt(0),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
return quote[0];
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
(0, context_1.printLog)(ctx, `quoteGasPoolCost — unavailable (${err instanceof Error ? err.message : String(err)})`);
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=gas-pool-quote.js.map
|
|
@@ -11,6 +11,11 @@ export interface GasSizingInput {
|
|
|
11
11
|
* Used to price gasFee in USD via PriceFetch (Chainlink ETH/USD, Pyth SOL/USD).
|
|
12
12
|
*/
|
|
13
13
|
destinationChain: CHAIN;
|
|
14
|
+
/** UniversalCore and gas token, for pool-valued destinations. */
|
|
15
|
+
pool?: {
|
|
16
|
+
universalCore: `0x${string}`;
|
|
17
|
+
gasToken: `0x${string}`;
|
|
18
|
+
};
|
|
14
19
|
}
|
|
15
20
|
export interface GasSizingDecision {
|
|
16
21
|
category: GasSizingCategory;
|
|
@@ -22,16 +22,21 @@ const chain_1 = require("../../constants/chain");
|
|
|
22
22
|
const price_fetch_1 = require("../../price-fetch/price-fetch");
|
|
23
23
|
const context_1 = require("./context");
|
|
24
24
|
const pc_usd_oracle_1 = require("./pc-usd-oracle");
|
|
25
|
+
const gas_pool_quote_1 = require("./gas-pool-quote");
|
|
25
26
|
// ============================================================================
|
|
26
27
|
// Thresholds (USD, 8 decimals)
|
|
27
28
|
// ============================================================================
|
|
28
29
|
const ONE_USD_8D = (0, viem_1.parseUnits)('1', 8); // 100_000_000
|
|
29
30
|
const TEN_USD_8D = (0, viem_1.parseUnits)('10', 8); // 1_000_000_000
|
|
31
|
+
/** EVM destinations whose gas token is not ETH. No USD feed exists for them
|
|
32
|
+
* (the BSC gateway's feed is ETH/USD), so their gas is valued at the Push pool
|
|
33
|
+
* rate the gateway's swap actually pays. */
|
|
34
|
+
const POOL_VALUED_GAS_CHAINS = new Set([enums_1.CHAIN.BNB_TESTNET]);
|
|
30
35
|
// ============================================================================
|
|
31
36
|
// Public API
|
|
32
37
|
// ============================================================================
|
|
33
38
|
async function sizeOutboundGas(ctx, input) {
|
|
34
|
-
const { gasFee, originChain, destinationChain } = input;
|
|
39
|
+
const { gasFee, originChain, destinationChain, pool } = input;
|
|
35
40
|
// 1) Price gasFee in USD (8 decimals). gasFee is denominated in the
|
|
36
41
|
// destination native (pETH = ETH value, pSOL = SOL value). The price
|
|
37
42
|
// feed lives on the *origin* side (origin gateway's Chainlink/Pyth),
|
|
@@ -40,20 +45,31 @@ async function sizeOutboundGas(ctx, input) {
|
|
|
40
45
|
const destVm = chain_1.CHAIN_INFO[destinationChain].vm;
|
|
41
46
|
const priceSourceChain = destVm === enums_1.VM.SVM ? enums_1.CHAIN.SOLANA_DEVNET : enums_1.CHAIN.ETHEREUM_SEPOLIA;
|
|
42
47
|
const destDecimals = destVm === enums_1.VM.SVM ? 9 : 18;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
const poolPcIn = pool && POOL_VALUED_GAS_CHAINS.has(destinationChain)
|
|
49
|
+
? await (0, gas_pool_quote_1.quoteGasPoolCost)(ctx, pool.universalCore, pool.gasToken, gasFee)
|
|
50
|
+
: undefined;
|
|
51
|
+
let gasUsd;
|
|
52
|
+
if (poolPcIn !== undefined) {
|
|
53
|
+
gasUsd = await (0, pc_usd_oracle_1.pcToUsd)(ctx, poolPcIn, originChain);
|
|
54
|
+
(0, context_1.printLog)(ctx, `sizeOutboundGas — gasFee=${gasFee.toString()} valued at the Push pool: ` +
|
|
55
|
+
`pcIn=${poolPcIn.toString()}, gasUsd=${gasUsd.toString()} (1e8)`);
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
else {
|
|
58
|
+
let destUsdPrice;
|
|
59
|
+
try {
|
|
60
|
+
destUsdPrice = await new price_fetch_1.PriceFetch(ctx.rpcUrls).getPrice(priceSourceChain);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
(0, context_1.printLog)(ctx, `sizeOutboundGas — PriceFetch.getPrice failed (${err instanceof Error ? err.message : String(err)}); treating as Case B passthrough`);
|
|
64
|
+
// Degrade gracefully: assume the raw gasFee is already a sane size and
|
|
65
|
+
// convert 1:1 to native PC. Callers keep their existing fallback logic.
|
|
66
|
+
return passthroughAsCaseB(ctx, gasFee, originChain);
|
|
67
|
+
}
|
|
68
|
+
const oneDestNativeUnit = (0, viem_1.parseUnits)('1', destDecimals);
|
|
69
|
+
gasUsd = (gasFee * destUsdPrice) / oneDestNativeUnit;
|
|
70
|
+
(0, context_1.printLog)(ctx, `sizeOutboundGas — gasFee=${gasFee.toString()} ${destVm === enums_1.VM.SVM ? 'pSOL' : 'gas token'} (${destDecimals}d), ` +
|
|
71
|
+
`destUsdPrice=${destUsdPrice.toString()} (1e8), gasUsd=${gasUsd.toString()} (1e8)`);
|
|
52
72
|
}
|
|
53
|
-
const oneDestNativeUnit = (0, viem_1.parseUnits)('1', destDecimals);
|
|
54
|
-
const gasUsd = (gasFee * destUsdPrice) / oneDestNativeUnit;
|
|
55
|
-
(0, context_1.printLog)(ctx, `sizeOutboundGas — gasFee=${gasFee.toString()} ${destVm === enums_1.VM.SVM ? 'pSOL' : 'pETH'} (${destDecimals}d), ` +
|
|
56
|
-
`destUsdPrice=${destUsdPrice.toString()} (1e8), gasUsd=${gasUsd.toString()} (1e8)`);
|
|
57
73
|
// 2) Categorize and compute target USD for each leg.
|
|
58
74
|
let category;
|
|
59
75
|
let gasLegUsd;
|
|
@@ -61,7 +61,7 @@ export interface SvmFinalizeGasBudgetInput {
|
|
|
61
61
|
}
|
|
62
62
|
export declare function gasLimitForSvmGasFeeBudget(gasFeeBudget: bigint, gasPrice: bigint): bigint;
|
|
63
63
|
type OutboundGasQuote = Awaited<ReturnType<typeof queryOutboundGasFee>>;
|
|
64
|
-
export declare function ensureSvmFinalizeGasBudgetQuote({ ctx, ueaAddress, targetChain, prc20Token, quote, splMintBase58, burnAmount, pathTag, pc20, requoteFn, }: {
|
|
64
|
+
export declare function ensureSvmFinalizeGasBudgetQuote({ ctx, ueaAddress, targetChain, prc20Token, quote, splMintBase58, burnAmount, pathTag, pc20, requoteFn, onMinimum, }: {
|
|
65
65
|
ctx: OrchestratorContext;
|
|
66
66
|
ueaAddress: `0x${string}`;
|
|
67
67
|
targetChain: ChainType;
|
|
@@ -80,6 +80,8 @@ export declare function ensureSvmFinalizeGasBudgetQuote({ ctx, ueaAddress, targe
|
|
|
80
80
|
* PC20 token, which has no PRC20 config.
|
|
81
81
|
*/
|
|
82
82
|
requoteFn?: (gasLimit: bigint) => Promise<OutboundGasQuote>;
|
|
83
|
+
/** Receives the finalize minimum, so strict callers can refuse a short quote. */
|
|
84
|
+
onMinimum?: (minGasFee: bigint) => void;
|
|
83
85
|
}): Promise<OutboundGasQuote>;
|
|
84
86
|
/**
|
|
85
87
|
* Returns the minimum SVM `gas_fee` budget in lamports for the gateway
|
|
@@ -108,7 +108,7 @@ function resolveSvmGasLimitUsed(quote) {
|
|
|
108
108
|
gasLimitUsed: gasLimitForSvmGasFeeBudget(quote.gasFee, quote.gasPrice),
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
|
-
async function ensureSvmFinalizeGasBudgetQuote({ ctx, ueaAddress, targetChain, prc20Token, quote, splMintBase58, burnAmount, pathTag, pc20, requoteFn, }) {
|
|
111
|
+
async function ensureSvmFinalizeGasBudgetQuote({ ctx, ueaAddress, targetChain, prc20Token, quote, splMintBase58, burnAmount, pathTag, pc20, requoteFn, onMinimum, }) {
|
|
112
112
|
const minGasFee = await getSvmFinalizeGasBudget({
|
|
113
113
|
ctx,
|
|
114
114
|
ueaAddress,
|
|
@@ -117,6 +117,7 @@ async function ensureSvmFinalizeGasBudgetQuote({ ctx, ueaAddress, targetChain, p
|
|
|
117
117
|
burnAmount,
|
|
118
118
|
pc20,
|
|
119
119
|
});
|
|
120
|
+
onMinimum?.(minGasFee);
|
|
120
121
|
if (quote.gasFee >= minGasFee) {
|
|
121
122
|
const resolvedQuote = resolveSvmGasLimitUsed(quote);
|
|
122
123
|
(0, context_1.printLog)(ctx, `${pathTag} — SVM finalize gas budget ok: gasFee=${resolvedQuote.gasFee.toString()}, ` +
|