@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 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. Protocol mock LINK, Moonwell/Venus mocks and Circle testnet USDC can differ from the supported source tokens; they remain unavailable for bridge funding until real matching routes exist.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushchain/core",
3
- "version": "6.0.27-alpha.4",
3
+ "version": "6.0.27-alpha.6",
4
4
  "homepage": "https://push.org",
5
5
  "keywords": [
6
6
  "pushchain",
@@ -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. Token symbols alone do not identify a route.',
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 ? BigInt(0) : FIRST_USE_EVM_GAS_LIMIT), chain);
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
- const gasLimit = p.gasLimit ?? BigInt(1_000_000);
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);
@@ -8,4 +8,6 @@ export declare function quoteSvmRequest(runtime: AgenticRuntime, snap: Snapshot,
8
8
  protocolFee: bigint;
9
9
  nativeValueForGas: bigint;
10
10
  gasLimitUsed: bigint;
11
+ gasFee?: bigint;
12
+ poolPcIn?: bigint;
11
13
  }>;
@@ -29,6 +29,8 @@ export declare function prepareSvmAgentExecution(runtime: AgenticRuntime, gen: A
29
29
  protocolFee: bigint;
30
30
  nativeValueForGas: bigint;
31
31
  gasLimitUsed: bigint;
32
+ gasFee?: bigint;
33
+ poolPcIn?: bigint;
32
34
  };
33
35
  data: `0x${string}`;
34
36
  }>;
@@ -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 their limits, then regenerate this preset.',
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 prc20_address_1 = require("../../universal/prc20-address");
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, prc20_address_1.getPRC20Address)({ chain, address: token }, { network });
31
+ const prc20 = (0, token_routes_1.resolveAgenticPrc20)({ chain, address: token }, network);
33
32
  return {
34
33
  token,
35
- prc20: (0, viem_1.getAddress)(prc20.address),
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}. Choose a supported asset. This exact token needs a verified Push PRC20 route before it can be used.`,
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
- q = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
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: q,
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.nativeValueForGas,
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, viem_1.getAddress)((0, prc20_address_1.getPRC20Address)({ chain, address: typeof token === 'string' ? token : token.address }, { network }).address);
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
@@ -180,7 +180,7 @@ const TOKEN_META = {
180
180
  WETH: {
181
181
  symbol: 'WETH',
182
182
  decimals: 18,
183
- address: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
183
+ address: '0x4200000000000000000000000000000000000006',
184
184
  mechanism: 'approve',
185
185
  },
186
186
  },
@@ -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
- let destUsdPrice;
44
- try {
45
- destUsdPrice = await new price_fetch_1.PriceFetch(ctx.rpcUrls).getPrice(priceSourceChain);
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
- catch (err) {
48
- (0, context_1.printLog)(ctx, `sizeOutboundGas — PriceFetch.getPrice failed (${err instanceof Error ? err.message : String(err)}); treating as Case B passthrough`);
49
- // Degrade gracefully: assume the raw gasFee is already a sane size and
50
- // convert 1:1 to native PC. Callers keep their existing fallback logic.
51
- return passthroughAsCaseB(ctx, gasFee, originChain);
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()}, ` +