@pushchain/core 6.0.27-alpha.2 → 6.0.27-alpha.3

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
@@ -91,7 +91,7 @@ An equality pin fixes an argument; a maximum pin bounds it. For EVM signatures,
91
91
 
92
92
  `PushChain.CONSTANTS.AGENTIC.WALLET` resolves to the AGW on Push, its CEA on an EVM destination, or its CEA/selected token ATA on Solana. Deployment addresses and encoding helpers are internal.
93
93
 
94
- Omitted `maxValue`, `assets[].maxTotal`, and `maxGasPerCall` default to **zero**. Asset totals meter tokens sent from Push; they do not meter trading or lending using capital already in the destination CEA. Specify positive caps for sends that need value, tokens or destination gas. Native value is limited per call; there is no public native lifetime-total cap.
94
+ For directly authored rules, omitted `maxValue`, `assets[].maxTotal`, and `maxGasPerCall` default to **zero**. Presets supply explicit policy values as described below. Asset totals meter tokens sent from Push; they do not meter trading or lending using capital already in the destination CEA. Specify positive caps for sends that need value, tokens or destination gas. Native value is limited per call; there is no public native lifetime-total cap.
95
95
 
96
96
  ## EVM destinations
97
97
 
@@ -171,7 +171,7 @@ The wire encoder uses uint32 maximum for unlimited native calls and 255 for unsp
171
171
 
172
172
  - DEXES: Uniswap V3 on Ethereum, Base and Arbitrum (mainnet and their Sepolia testnets); PancakeSwap V3 on BNB mainnet and BSC testnet.
173
173
  - LENDING: Aave on Ethereum, Base, Arbitrum and BNB mainnets, plus Sepolia/Base Sepolia/Arbitrum Sepolia; Moonwell on Base mainnet/testnet and Venus on BNB mainnet/testnet.
174
- - STAKING: EigenLayer deposits on Ethereum mainnet and Sepolia; Lido stake and wrap/unwrap on Ethereum mainnet. Withdrawal requests with dynamic amounts, claims with position-derived amounts and EigenLayer withdrawals are excluded from these bounded presets. A selected native asset supplies the Lido `submit` value cap; omitted caps remain zero.
174
+ - STAKING: EigenLayer deposits on Ethereum mainnet and Sepolia; Lido stake and wrap/unwrap on Ethereum mainnet. Withdrawal requests with dynamic amounts, claims with position-derived amounts and EigenLayer withdrawals are excluded from these bounded presets. A selected native asset supplies the Lido `submit` value cap; omitted caps in explicitly supplied assets remain zero.
175
175
  - PREDICTION: no entries yet. Hide categories with empty `chains` lists.
176
176
 
177
177
  ```ts
@@ -180,13 +180,24 @@ const availablePresets = presets.filter(preset => preset.chains.length > 0);
180
180
  const { rules: reviewTemplates } = PushChain.utils.agentic.presets.get(
181
181
  PushChain.CONSTANTS.AGENTIC.PRESETS.CATEGORY.DEXES,
182
182
  );
183
- // Choose tokens in the review UI, then call get again as below.
183
+ // Every populated chain includes curated destination tokens in rule.assets.
184
+ // The SDK supplies spending limits, a PC budget and a seven-day expiry.
185
+ // Supply the agent to get complete Rule[] configuration.
186
+ const configuredPreset = PushChain.utils.agentic.presets.get(
187
+ PushChain.CONSTANTS.AGENTIC.PRESETS.CATEGORY.LENDING,
188
+ { chainNamespace: 'eip155:11155111', agent: '0x0000000000000000000000000000000000001234', funding: 'destination' },
189
+ );
190
+ // Destination assets must already be funded; no Push token exports are granted.
191
+ // Show configuredPreset.readiness.chains[0].selectedAssets in the review UI.
192
+ // Pass configuredPreset.rules directly to create after checking readiness.
193
+ // The helper below illustrates explicit overrides, rather than required UI input.
184
194
 
185
195
  function filledPreset(agent: Address, tokenIn: Address, tokenOut: Address) {
186
196
  return PushChain.utils.agentic.presets.get(
187
197
  PushChain.CONSTANTS.AGENTIC.PRESETS.CATEGORY.DEXES,
188
198
  {
189
199
  chainNamespace: 'eip155:11155111',
200
+ funding: 'destination',
190
201
  agent,
191
202
  validUntil: Math.floor(Date.now() / 1000) + 3600,
192
203
  maxGasPerCall: BigInt(10) ** BigInt(18),
@@ -199,9 +210,9 @@ function filledPreset(agent: Address, tokenIn: Address, tokenOut: Address) {
199
210
  }
200
211
  ```
201
212
 
202
- The example caps are illustrative raw units; choose caps for the actual token decimals and gas requirements. `get` groups calls into one rule per chain. The result is `{ rules, readiness }`. With agent and expiry supplied, `rules` is typed as `Rule[]`; otherwise it is `PresetRuleTemplate[]`. This type distinction alone does not establish readiness. Assets must name their chain unless a single chain filter is supplied. Omitted asset caps and `maxGasPerCall` default to zero.
213
+ The example caps are illustrative raw units; choose caps for the actual token decimals and gas requirements. `get` groups calls into one rule per chain. The result is `{ rules, readiness }`. With an agent supplied, `rules` is typed as `Rule[]`; otherwise it is `PresetRuleTemplate[]` with a generated expiry and missing agent. This type distinction alone does not establish readiness. When `assets` is omitted, each chain includes its curated destination tokens with the explicit spending limits below. An explicit `assets` array replaces the defaults for every returned chain; `[]` selects no tokens. Assets in an explicit list must name their chain unless a single chain filter is supplied. For an explicit asset list, omitted caps still default to zero. An omitted gas budget uses the preset policy; an explicit zero remains zero.
203
214
 
204
- DEX discovery works without options, with an empty asset list, or with assets for only some chains. A chain with fewer than two tokens returns full-signature review calls with wallet-recipient pins and an explicit zero input-amount maximum. A single supplied token is also pinned as the input. These calls cannot spend swap input tokens, even if asset budgets are filled later. Call `get` again after selecting both tokens to replace the review calls with the complete token-pinned rules; merely assigning `rule.assets` is insufficient.
215
+ DEX discovery without `assets` includes a curated wrapped-native/stablecoin pair per chain, with both swap directions, exact-input/output functions and the token-specific input caps below. It does not require the UI to select tokens separately for every network. DEX discovery also works with an explicit empty asset list or with assets for only some chains. A chain with fewer than two explicitly supplied tokens returns full-signature review calls with wallet-recipient pins and an explicit zero input-amount maximum. A single supplied token is also pinned as the input. These calls cannot spend swap input tokens, even if asset budgets are filled later. Call `get` again after selecting both tokens to replace the review calls with the complete token-pinned rules; merely assigning `rule.assets` is insufficient.
205
216
 
206
217
  With two ERC20 tokens, `get` expands ordered pairs into exact-input and exact-output calls with token, wallet-recipient and input-amount pins. Native markers and expansions exceeding the policy's 32-pin limit are rejected. Two tokens fit the shipped router template; three do not. Static tuple fields use paths such as `[0, 3]`.
207
218
 
@@ -209,11 +220,55 @@ Reviewed EVM spenders generate approvals pinned to that spender and bounded by t
209
220
 
210
221
  Templates describe permissions, not available liquidity or guaranteed execution. Verify destination token balances, supported reserves and source PRC20 routes before use: protocol testnet mock tokens may differ from MOVEABLE tokens. Presets do not sign, fetch app ABIs or modify wallets. Dynamic-path swaps, Solana protocol templates and unconfirmed deployments are excluded. Mainnet addresses were checked read-only; no mainnet transactions were executed. Other mainnet protocols in the proposal remain excluded until their deployment and permission constraints are verified. Local policy tests do not execute the destination protocols.
211
222
 
223
+ ### Curated token defaults
224
+
225
+ These are small, protocol-specific subsets chosen to fit the 32-call/32-pin ceilings. The full reserve catalog is not automatically granted.
226
+
227
+ | Network | DEX pair | Lending tokens |
228
+ | --- | --- | --- |
229
+ | Ethereum | WETH / USDC | WETH, USDC |
230
+ | Base | WETH / USDC | USDC (Aave and Moonwell) |
231
+ | Arbitrum | WETH / native USDC | WETH, native USDC |
232
+ | BNB | WBNB / USDT | USDT (Aave and Venus) |
233
+ | Sepolia | DEX WETH / Circle USDC | Aave faucet LINK |
234
+ | Base Sepolia | WETH / Circle USDC | Aave faucet LINK, Moonwell mock USDbC and WETH |
235
+ | Arbitrum Sepolia | WETH / Circle USDC | Aave USDC |
236
+ | BSC testnet | WBNB / Venus USDT | Venus USDT, USDC |
237
+
238
+ 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. These tokens may lack Push PRC20 routes and can instead be used with destination-held balances. Defaults do not guarantee active reserves, liquidity, a Push PRC20 route or execution. In particular, an unmodified preset can fail rule preparation if its default assets lack PRC20 mappings. For destination-only use, pass `funding: 'destination'`: the SDK generates the operation constraints, returns empty rule bridge assets and retains selected token limits in readiness metadata. This grants no token export budget from Push and requires already-funded destination balances. Token addresses are returned as before; metadata was verified when defining the policy. No runtime metadata/price fetch is added to get.
239
+
240
+ ### Funding and release-network checks
241
+
242
+ `funding` defaults to `'push'`. Missing PRC20 registry entries produce `BRIDGE_ASSET_UNAVAILABLE`, and readiness is false while the returned rule retains all requested assets. No address or cap is silently replaced. `network` defaults to `TESTNET_DONUT` (`TESTNET` is accepted as an alias); use the connected client's Push network when supplying it. Networks without a verified AGW deployment report `AGW_NETWORK_UNAVAILABLE`.
243
+
244
+ Use `funding: 'destination'` for the prefunded CEA workflow. Selected tokens still generate the same target/function/amount/recipient/spender restrictions, exposed alongside their caps in `readiness.chains[].selectedAssets`. Returned `rule.assets` is intentionally empty, which grants zero Push token-export authority. It is safe to pass these returned rules directly to create/update. The mode does not fund the destination, wrap ETH, mint testnet tokens or enable an unsupported network. Do not relabel it as bridge funding in the UI.
245
+
246
+ 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.
247
+
248
+ ### Default permission policy
249
+
250
+ Only preset-generated assets receive these positive values. Direct rules, unknown tokens and explicit asset lists do not acquire guessed limits.
251
+
252
+ | Selected token | Per allowed call / Push outbound | Cumulative Push-outbound total |
253
+ | --- | --- | --- |
254
+ | USDC / USDT / mock USDbC | 10 tokens | 100 tokens |
255
+ | ETH / WETH / stETH / wstETH | 0.001 token | 0.01 token |
256
+ | WBNB | 0.01 token | 0.1 token |
257
+ | LINK | 1 token | 10 tokens |
258
+
259
+ Amounts use verified token decimals, keyed by chain and address, rather than symbol or live prices. For example, the USDT per-call limit is `10 * 10^18` on BNB mainnet and `10 * 10^6` on BSC testnet. These are fixed token quantities, not USD budgets.
260
+
261
+ The default expiry is seven days from each get call, computed once for all returned rules. The default gas ceiling is **20 PC per outbound**, including the value checked by URP; it is not a flat fee or a reservation. Actual sends obtain a fresh quote and remain subject to the rule cap. This ceiling has testnet protocol-flow coverage; mainnet templates do not have funded execution acceptance. Quotes may exceed the cap, requiring an explicit higher budget.
262
+
263
+ Explicit `validUntil` and `maxGasPerCall` override policy values; zero gas is preserved and reports incomplete readiness. An explicit `assets` array replaces all default tokens and limits, and omitted caps inside it remain zero. To change a preset cap while retaining its token set, take the returned assets, replace the caps and pass them back to get. Do not just edit the returned rule assets: the allowed-call pins and approvals also need regeneration.
264
+
265
+ `maxPerCall` applies to each allowed destination call; multiple calls may each consume that amount. `maxTotal` meters assets sent from Push, not aggregate destination activity. No destination lifetime limit is implied by these defaults. The general rules API continues to treat omitted caps as zero.
266
+
212
267
  ### Configure, inspect and regenerate
213
268
 
214
- `readiness.ready` means the returned chains have locally complete inputs and at least one enabled asset operation. It does not check PRC20 availability, deployments, balances, live reserve flags or liquidity. `readiness.chains` gives per-chain `issues`, `disabledCalls`, `excludedCalls`, catalog `knownTokens` and `zeroBridgeLimits`. These are additive result fields; `rules` remains accepted by the existing create/update methods.
269
+ `readiness.ready` requires complete configuration plus offline SDK deployment/destination/bridge-registry support for the selected funding mode. Per-chain `configurationReady` reports input completeness separately. These checks do not verify live balances, reserve flags, liquidity, current deployed bytecode or source identity; grant preparation still verifies wiring and token metadata on-chain before signing. `readiness.chains` gives per-chain `issues`, `disabledCalls`, `excludedCalls`, catalog `knownTokens`, `selectedAssets`, `bridgeAssets`, `funding` and `zeroBridgeLimits`. Display `selectedAssets` as the reviewed token/amount permissions in either mode; `rule.assets` describes Push bridge permissions only. `bridgeAssets` reports registry presence, not verified live deployment. Mainnet templates remain discoverable but have `DESTINATION_UNAVAILABLE` on Donut. These are additive result fields; `rules` remains accepted by the existing create/update methods.
215
270
 
216
- The UI should collect chain, agent, token selections, raw-unit limits, gas budget and expiry. Show zero-cap calls as disabled. Present excluded calls explicitly. There is no SDK-selected 0.1 PC gas budget or 30-day expiry. A zero bridge limit can be intentional when assets are already at the destination; it is reported separately from operation readiness.
271
+ Tokens and permission defaults come directly from the SDK: omitted `assets` populates the curated subset and its caps in every returned `rule.assets`. Omitted gas and expiry use the policy below. The caller supplies the agent and can filter chains; no per-network token selection is required. Show the generated permissions for owner review and allow explicit overrides. Regenerate via get after an edit so operation pins/approvals match the new caps. Show explicitly zero-cap calls as disabled and present excluded calls clearly. A zero bridge limit can be intentional when assets are already at the destination; it is reported separately from operation readiness.
217
272
 
218
273
  ```ts
219
274
  async function createConfiguredDex(
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ @pushchain/core@6.0.27-alpha.3 (2026-10-18)
2
+
3
+ - feat: ship bounded preset defaults with funding checks
4
+
5
+ ---
6
+
1
7
  @pushchain/core@6.0.27-alpha.2 (2026-10-16)
2
8
 
3
9
  - feat: add bounded preset operations and configuration readiness
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushchain/core",
3
- "version": "6.0.27-alpha.2",
3
+ "version": "6.0.27-alpha.3",
4
4
  "homepage": "https://push.org",
5
5
  "keywords": [
6
6
  "pushchain",
@@ -22,5 +22,5 @@ export declare const PRC20_SOURCE_ABI: readonly [{
22
22
  export declare function readSvmSourceToken(snap: Snapshot, token: Address, chain: string, network?: PUSH_NETWORK): Promise<Hex>;
23
23
  export declare const CORE_ADDRESS: Address;
24
24
  export declare function readGasPrc20(snap: Snapshot, chain: string): Promise<Address>;
25
- /** Donut native precompile returns a zero word; ERC20 metadata is a dynamic string. */
25
+ /** Solidity PRC20s return a string; Donut precompiles can return an address word. */
26
26
  export declare function readOriginToken(snap: Snapshot, token: Address, chain: string): Promise<Address>;
@@ -64,19 +64,25 @@ exports.CORE_ADDRESS = '0x00000000000000000000000000000000000000C0';
64
64
  async function readGasPrc20(snap, chain) {
65
65
  return (0, viem_1.getAddress)(await snap.read(exports.CORE_ADDRESS, prc20_evm_1.UNIVERSAL_CORE_EVM, 'gasTokenPRC20ByChainNamespace', [chain]));
66
66
  }
67
- /** Donut native precompile returns a zero word; ERC20 metadata is a dynamic string. */
67
+ /** Solidity PRC20s return a string; Donut precompiles can return an address word. */
68
68
  async function readOriginToken(snap, token, chain) {
69
69
  let source;
70
70
  try {
71
71
  source = await snap.read(token, exports.PRC20_SOURCE_ABI, 'SOURCE_TOKEN_ADDRESS');
72
72
  }
73
73
  catch (cause) {
74
- if ((await readGasPrc20(snap, chain)) !== (0, viem_1.getAddress)(token))
74
+ let sourceWord;
75
+ try {
76
+ sourceWord = (0, viem_1.getAddress)(await snap.read(token, NATIVE_SOURCE_ABI, 'SOURCE_TOKEN_ADDRESS'));
77
+ }
78
+ catch {
75
79
  throw cause;
76
- const native = await snap.read(token, NATIVE_SOURCE_ABI, 'SOURCE_TOKEN_ADDRESS');
77
- if ((0, viem_1.getAddress)(native) === viem_1.zeroAddress)
80
+ }
81
+ if (sourceWord !== viem_1.zeroAddress)
82
+ return sourceWord;
83
+ if ((await readGasPrc20(snap, chain)) === (0, viem_1.getAddress)(token))
78
84
  return viem_1.zeroAddress;
79
- throw cause;
85
+ throw new errors_1.AgenticError(errors_1.AGENTIC_ERROR_CODE.RULE_READ_FAILED, `non-native PRC20 ${token} has an empty source token address`);
80
86
  }
81
87
  if ((0, viem_1.isAddress)(source, { strict: false }))
82
88
  return (0, viem_1.getAddress)(source);
@@ -25,6 +25,8 @@ export interface PresetCallDefinition extends RuleCall {
25
25
  /** Internal catalog: reviewed addresses and explicit calls only; never fetched at runtime. */
26
26
  export interface PresetChainDefinition {
27
27
  chainNamespace: ForeignChainNamespace;
28
+ /** Used with the bundled permission policy only when assets is omitted. */
29
+ defaultTokens?: readonly Address[];
28
30
  allowedCalls: readonly PresetCallDefinition[];
29
31
  /** Static single-hop routers; incomplete pairs return zero-input-cap review calls. */
30
32
  swapRouters?: readonly {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PRESET_CATALOG = void 0;
4
4
  const viem_1 = require("viem");
5
5
  const aave_reserves_1 = require("./aave-reserves");
6
+ const default_tokens_1 = require("./default-tokens");
6
7
  /** Mainnet and testnet templates from the October 15 Notion export and protocol sources.
7
8
  * Read-only deployment/code evidence and exclusions: plan/agw/research/preset-catalog-2026-10-15/.
8
9
  * Mainnet entries are templates only; no mainnet deployment or execution guarantee.
@@ -153,6 +154,7 @@ exports.PRESET_CATALOG = [
153
154
  ],
154
155
  ].map(([chain, router, app]) => ({
155
156
  chainNamespace: chain,
157
+ defaultTokens: default_tokens_1.DEX_DEFAULT_TOKENS[chain],
156
158
  allowedCalls: [],
157
159
  swapRouters: [{ address: address(router), app }],
158
160
  approvals: [{ spender: address(router), app }],
@@ -169,6 +171,7 @@ exports.PRESET_CATALOG = [
169
171
  ['eip155:56', '0x6807dc923806fE8Fd134338EABCA509979a7e0cB'],
170
172
  ].map(([chainNamespace, pool]) => ({
171
173
  chainNamespace,
174
+ defaultTokens: default_tokens_1.LENDING_DEFAULT_TOKENS[chainNamespace],
172
175
  allowedCalls: [
173
176
  ...aave(address(pool), chainNamespace),
174
177
  ...mainnetMarkets(chainNamespace).allowedCalls,
@@ -184,6 +187,7 @@ exports.PRESET_CATALOG = [
184
187
  })),
185
188
  {
186
189
  chainNamespace: 'eip155:11155111',
190
+ defaultTokens: default_tokens_1.LENDING_DEFAULT_TOKENS['eip155:11155111'],
187
191
  allowedCalls: aave(sepoliaPool, 'eip155:11155111'),
188
192
  approvals: [
189
193
  {
@@ -195,6 +199,7 @@ exports.PRESET_CATALOG = [
195
199
  },
196
200
  {
197
201
  chainNamespace: 'eip155:421614',
202
+ defaultTokens: default_tokens_1.LENDING_DEFAULT_TOKENS['eip155:421614'],
198
203
  allowedCalls: aave(arbitrumPool, 'eip155:421614'),
199
204
  approvals: [
200
205
  {
@@ -206,6 +211,7 @@ exports.PRESET_CATALOG = [
206
211
  },
207
212
  {
208
213
  chainNamespace: 'eip155:84532',
214
+ defaultTokens: default_tokens_1.LENDING_DEFAULT_TOKENS['eip155:84532'],
209
215
  allowedCalls: [
210
216
  ...aave(basePool, 'eip155:84532'),
211
217
  ...market(moonUSDBC, 'Moonwell mUSDbC', address('0x4616975f7E6045FD9737e95bC527e9e14c727BD2')),
@@ -238,6 +244,7 @@ exports.PRESET_CATALOG = [
238
244
  },
239
245
  {
240
246
  chainNamespace: 'eip155:97',
247
+ defaultTokens: default_tokens_1.LENDING_DEFAULT_TOKENS['eip155:97'],
241
248
  allowedCalls: [
242
249
  ...market(venusUSDT, 'Venus vUSDT', address('0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c')),
243
250
  ...market(venusUSDC, 'Venus vUSDC', address('0x16227D60f7a0e586C66B005219dfc887D13C9531')),
@@ -270,6 +277,7 @@ exports.PRESET_CATALOG = [
270
277
  chains: [
271
278
  {
272
279
  chainNamespace: 'eip155:1',
280
+ defaultTokens: default_tokens_1.STAKING_DEFAULT_TOKENS['eip155:1'],
273
281
  allowedCalls: [
274
282
  {
275
283
  target: stETH,
@@ -333,6 +341,7 @@ exports.PRESET_CATALOG = [
333
341
  },
334
342
  {
335
343
  chainNamespace: 'eip155:11155111',
344
+ defaultTokens: default_tokens_1.STAKING_DEFAULT_TOKENS['eip155:11155111'],
336
345
  allowedCalls: [
337
346
  {
338
347
  target: eigen,
@@ -0,0 +1,9 @@
1
+ import type { Address } from 'viem';
2
+ import type { RuleAsset } from '../agentic.types';
3
+ /** Preset-only permission policy. Ordinary rule authoring retains zero defaults.
4
+ * Amounts are token quantities, not USD valuations or destination lifetime caps.
5
+ * Metadata verified read-only on October 18; evidence is retained under plan/agw/.
6
+ */
7
+ export declare const PRESET_VALIDITY_SECONDS: number;
8
+ export declare const PRESET_GAS_BUDGETS: Readonly<Record<string, bigint>>;
9
+ export declare function defaultPresetAsset(chain: string, token: Address): RuleAsset;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRESET_GAS_BUDGETS = exports.PRESET_VALIDITY_SECONDS = void 0;
4
+ exports.defaultPresetAsset = defaultPresetAsset;
5
+ const viem_1 = require("viem");
6
+ const errors_1 = require("../errors");
7
+ /** Preset-only permission policy. Ordinary rule authoring retains zero defaults.
8
+ * Amounts are token quantities, not USD valuations or destination lifetime caps.
9
+ * Metadata verified read-only on October 18; evidence is retained under plan/agw/.
10
+ */
11
+ exports.PRESET_VALIDITY_SECONDS = 7 * 24 * 60 * 60;
12
+ // Explicit profiles keep future chains from inheriting an unreviewed budget.
13
+ // 20 PC is a ceiling, not the amount charged: execution obtains a fresh quote.
14
+ // This budget has testnet protocol-flow coverage; mainnet remains template-only.
15
+ exports.PRESET_GAS_BUDGETS = Object.fromEntries([
16
+ 'eip155:1',
17
+ 'eip155:8453',
18
+ 'eip155:42161',
19
+ 'eip155:56',
20
+ 'eip155:11155111',
21
+ 'eip155:84532',
22
+ 'eip155:421614',
23
+ 'eip155:97',
24
+ ].map((chain) => [chain, (0, viem_1.parseUnits)('20', 18)]));
25
+ const stable6 = { decimals: 6, perCall: '10', total: '100' };
26
+ const stable18 = { ...stable6, decimals: 18 };
27
+ const eth = { decimals: 18, perCall: '0.001', total: '0.01' };
28
+ const bnb = { decimals: 18, perCall: '0.01', total: '0.1' };
29
+ const link = { decimals: 18, perCall: '1', total: '10' };
30
+ // Match by chain AND address. Never infer decimals or limits from a symbol,
31
+ // caller metadata, or the position of a token in an arbitrary selection.
32
+ const TOKEN_BUDGETS = {
33
+ 'eip155:1': [
34
+ ['0x0000000000000000000000000000000000000000', eth],
35
+ ['0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', eth],
36
+ ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', stable6],
37
+ ['0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', eth],
38
+ ['0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', eth],
39
+ ],
40
+ 'eip155:8453': [
41
+ ['0x4200000000000000000000000000000000000006', eth],
42
+ ['0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', stable6],
43
+ ],
44
+ 'eip155:42161': [
45
+ ['0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', eth],
46
+ ['0xaf88d065e77c8cC2239327C5EDb3A432268e5831', stable6],
47
+ ],
48
+ 'eip155:56': [
49
+ ['0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', bnb],
50
+ ['0x55d398326f99059fF775485246999027B3197955', stable18],
51
+ ],
52
+ 'eip155:11155111': [
53
+ ['0xfff9976782d46cc05630d1f6ebab18b2324d6b14', eth],
54
+ ['0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', stable6],
55
+ ['0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5', link],
56
+ ['0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', eth],
57
+ ],
58
+ 'eip155:84532': [
59
+ ['0x4200000000000000000000000000000000000006', eth],
60
+ ['0x036CbD53842c5426634e7929541eC2318f3dCF7e', stable6],
61
+ ['0x810D46F9a9027E28F9B01F75E2bdde839dA61115', link],
62
+ ['0x4616975f7E6045FD9737e95bC527e9e14c727BD2', stable6],
63
+ ['0x663A6F02d547dFd639BF689FED298712f2FB69Fb', eth],
64
+ ],
65
+ 'eip155:421614': [
66
+ ['0x980B62Da83eFf3D4576C647993b0c1D7faf17c73', eth],
67
+ ['0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d', stable6],
68
+ ],
69
+ 'eip155:97': [
70
+ ['0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd', bnb],
71
+ ['0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c', stable6],
72
+ ['0x16227D60f7a0e586C66B005219dfc887D13C9531', stable6],
73
+ ],
74
+ };
75
+ function defaultPresetAsset(chain, token) {
76
+ const budget = TOKEN_BUDGETS[chain]?.find(([address]) => (0, viem_1.getAddress)(address) === (0, viem_1.getAddress)(token))?.[1];
77
+ if (!budget)
78
+ throw (0, errors_1.capabilityUnavailable)('presetPolicy', `No reviewed default spending policy for ${chain}:${token}`);
79
+ return {
80
+ token,
81
+ maxPerCall: (0, viem_1.parseUnits)(budget.perCall, budget.decimals),
82
+ maxTotal: (0, viem_1.parseUnits)(budget.total, budget.decimals),
83
+ };
84
+ }
85
+ //# sourceMappingURL=default-policy.js.map
@@ -0,0 +1,4 @@
1
+ import { type Address } from 'viem';
2
+ export declare const DEX_DEFAULT_TOKENS: Readonly<Record<string, readonly Address[]>>;
3
+ export declare const LENDING_DEFAULT_TOKENS: Readonly<Record<string, readonly Address[]>>;
4
+ export declare const STAKING_DEFAULT_TOKENS: Readonly<Record<string, readonly Address[]>>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STAKING_DEFAULT_TOKENS = exports.LENDING_DEFAULT_TOKENS = exports.DEX_DEFAULT_TOKENS = void 0;
4
+ const viem_1 = require("viem");
5
+ /** Curated subsets of the reviewed catalog. Addresses are destination tokens,
6
+ * not a promise that a Push PRC20 route or protocol liquidity is available.
7
+ * Keep these sets within the policy's 32-call/32-pin limits after expansion.
8
+ * Testnet DEX pairs and lending/staking assets match the live preset fixtures.
9
+ * Mainnet addresses share the catalog's October 15 read-only source baseline.
10
+ * Spending caps are intentionally separate from token selection.
11
+ */
12
+ const addresses = (...values) => values.map((value) => (0, viem_1.getAddress)(value.toLowerCase()));
13
+ exports.DEX_DEFAULT_TOKENS = {
14
+ 'eip155:1': addresses('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
15
+ 'eip155:8453': addresses('0x4200000000000000000000000000000000000006', '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'),
16
+ 'eip155:42161': addresses('0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'),
17
+ 'eip155:56': addresses('0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', '0x55d398326f99059fF775485246999027B3197955'),
18
+ 'eip155:11155111': addresses('0xfff9976782d46cc05630d1f6ebab18b2324d6b14', '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'),
19
+ 'eip155:84532': addresses('0x4200000000000000000000000000000000000006', '0x036CbD53842c5426634e7929541eC2318f3dCF7e'),
20
+ 'eip155:421614': addresses('0x980B62Da83eFf3D4576C647993b0c1D7faf17c73', '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'),
21
+ 'eip155:97': addresses('0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd', '0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c'),
22
+ };
23
+ exports.LENDING_DEFAULT_TOKENS = {
24
+ 'eip155:1': exports.DEX_DEFAULT_TOKENS['eip155:1'],
25
+ // One shared reserve/market token keeps Aave + Moonwell/Venus within 32 pins.
26
+ 'eip155:8453': addresses('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'),
27
+ 'eip155:42161': exports.DEX_DEFAULT_TOKENS['eip155:42161'],
28
+ 'eip155:56': addresses('0x55d398326f99059fF775485246999027B3197955'),
29
+ // Protocol testnet reserves differ from Circle USDC and DEX wrapped ETH.
30
+ 'eip155:11155111': addresses('0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5'),
31
+ 'eip155:84532': addresses('0x810D46F9a9027E28F9B01F75E2bdde839dA61115', '0x4616975f7E6045FD9737e95bC527e9e14c727BD2', '0x663A6F02d547dFd639BF689FED298712f2FB69Fb'),
32
+ 'eip155:421614': addresses('0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'),
33
+ 'eip155:97': addresses('0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c', '0x16227D60f7a0e586C66B005219dfc887D13C9531'),
34
+ };
35
+ exports.STAKING_DEFAULT_TOKENS = {
36
+ 'eip155:1': addresses(viem_1.zeroAddress, '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0'),
37
+ 'eip155:11155111': addresses('0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9'),
38
+ };
39
+ //# sourceMappingURL=default-tokens.js.map
@@ -7,6 +7,9 @@ const errors_1 = require("../errors");
7
7
  const selectors_1 = require("../codec/selectors");
8
8
  const catalog_1 = require("./catalog");
9
9
  const operation_limits_1 = require("./operation-limits");
10
+ const enums_1 = require("../../constants/enums");
11
+ const routes_1 = require("./routes");
12
+ const default_policy_1 = require("./default-policy");
10
13
  const MAX_UINT256 = (BigInt(1) << BigInt(256)) - BigInt(1);
11
14
  const invalid = (message) => new errors_1.AgenticError(errors_1.AGENTIC_ERROR_CODE.INVALID_RULE, message);
12
15
  function cap(value, name) {
@@ -51,6 +54,7 @@ function buildPresetApi(catalog) {
51
54
  ...p,
52
55
  chains: p.chains.map((c) => ({
53
56
  ...c,
57
+ ...(c.defaultTokens ? { defaultTokens: [...c.defaultTokens] } : {}),
54
58
  allowedCalls: c.allowedCalls.map((call) => ({
55
59
  ...copyCall(call),
56
60
  ...(call.budget
@@ -93,15 +97,23 @@ function buildPresetApi(catalog) {
93
97
  throw invalid(`unknown preset ${String(preset)}`);
94
98
  if (!options || typeof options !== 'object' || Array.isArray(options))
95
99
  throw invalid('preset options must be an object');
100
+ const now = Math.floor(Date.now() / 1000);
101
+ const network = options.network ?? enums_1.PUSH_NETWORK.TESTNET_DONUT;
102
+ const funding = options.funding ?? 'push';
103
+ if (!Object.values(enums_1.PUSH_NETWORK).includes(network))
104
+ throw invalid('unknown preset Push network');
105
+ if (funding !== 'push' && funding !== 'destination')
106
+ throw invalid('preset funding must be push or destination');
96
107
  if (options.agent !== undefined &&
97
108
  (!(0, viem_1.isAddress)(options.agent, { strict: false }) ||
98
109
  (0, viem_1.getAddress)(options.agent) === viem_1.zeroAddress))
99
110
  throw invalid('preset agent must be a nonzero Push address');
100
111
  if (options.validUntil !== undefined &&
101
112
  (!Number.isSafeInteger(options.validUntil) ||
102
- options.validUntil <= Math.floor(Date.now() / 1000) ||
113
+ options.validUntil <= now ||
103
114
  options.validUntil >= 2 ** 48))
104
115
  throw invalid('preset validUntil must be a future uint48 timestamp');
116
+ const validUntil = options.validUntil ?? now + default_policy_1.PRESET_VALIDITY_SECONDS;
105
117
  if (options.maxGasPerCall !== undefined)
106
118
  cap(options.maxGasPerCall, 'maxGasPerCall');
107
119
  if (options.assets !== undefined && !Array.isArray(options.assets))
@@ -114,6 +126,12 @@ function buildPresetApi(catalog) {
114
126
  if (!chains.length && options.assets?.length)
115
127
  throw (0, errors_1.capabilityUnavailable)('presetCatalog', `preset ${preset} has no approved app entries`);
116
128
  const assets = new Map();
129
+ // An explicit array (including []) replaces the defaults for all selected
130
+ // chains. Never add permissions on chains the caller left unconfigured.
131
+ if (options.assets === undefined)
132
+ for (const chain of chains)
133
+ if (chain.defaultTokens)
134
+ assets.set(chain.chainNamespace, chain.defaultTokens.map((token) => (0, default_policy_1.defaultPresetAsset)(chain.chainNamespace, token)));
117
135
  for (const asset of options.assets ?? []) {
118
136
  if (!asset || typeof asset !== 'object')
119
137
  throw invalid('preset asset must be an object');
@@ -143,6 +161,11 @@ function buildPresetApi(catalog) {
143
161
  }
144
162
  const readiness = [];
145
163
  const rules = chains.map((chain) => {
164
+ const maxGasPerCall = options.maxGasPerCall ??
165
+ chain.maxGasPerCall ??
166
+ (chain.defaultTokens
167
+ ? default_policy_1.PRESET_GAS_BUDGETS[chain.chainNamespace]
168
+ : undefined);
146
169
  const selectedAssets = assets.get(chain.chainNamespace) ?? [];
147
170
  const limits = (0, operation_limits_1.applyOperationLimits)(chain.allowedCalls.map((c) => ({
148
171
  ...copyCall(c),
@@ -282,13 +305,7 @@ function buildPresetApi(catalog) {
282
305
  code: 'AGENT_REQUIRED',
283
306
  message: 'Choose the agent Push address.',
284
307
  });
285
- if (!options.validUntil)
286
- issues.push({
287
- code: 'EXPIRY_REQUIRED',
288
- message: 'Choose an expiry.',
289
- });
290
- if ((options.maxGasPerCall ?? chain.maxGasPerCall ?? BigInt(0)) ===
291
- BigInt(0))
308
+ if ((maxGasPerCall ?? BigInt(0)) === BigInt(0))
292
309
  issues.push({
293
310
  code: 'GAS_BUDGET_REQUIRED',
294
311
  message: 'Set a positive PC gas budget per outbound.',
@@ -308,14 +325,27 @@ function buildPresetApi(catalog) {
308
325
  code: 'NO_ENABLED_OPERATION',
309
326
  message: 'No asset operation has a positive per-call limit.',
310
327
  });
328
+ const configurationReady = issues.length === 0;
329
+ const routes = (0, routes_1.presetRoutes)(chain.chainNamespace, selectedAssets, network, funding);
330
+ issues.push(...routes.issues);
331
+ const bridgeAssets = funding === 'destination' ? [] : selectedAssets;
311
332
  readiness.push({
312
333
  chainNamespace: chain.chainNamespace,
313
334
  ready: issues.length === 0,
335
+ configurationReady,
336
+ funding,
337
+ selectedAssets: selectedAssets.map((a) => ({
338
+ ...a,
339
+ token: typeof a.token === 'string' ? a.token : { ...a.token },
340
+ })),
341
+ bridgeAssets: routes.bridgeAssets,
314
342
  issues,
315
343
  disabledCalls: limits.disabledCalls,
316
344
  excludedCalls: limits.excludedCalls,
317
- knownTokens: limits.knownTokens,
318
- zeroBridgeLimits: selectedAssets
345
+ knownTokens: [
346
+ ...new Set([...limits.knownTokens, ...(chain.defaultTokens ?? [])]),
347
+ ],
348
+ zeroBridgeLimits: bridgeAssets
319
349
  .filter((a) => a.maxPerCall === BigInt(0) || a.maxTotal === BigInt(0))
320
350
  .map(operation_limits_1.assetAddress),
321
351
  });
@@ -325,16 +355,12 @@ function buildPresetApi(catalog) {
325
355
  ...(options.agent !== undefined
326
356
  ? { agent: (0, viem_1.getAddress)(options.agent) }
327
357
  : {}),
328
- ...(options.validUntil !== undefined
329
- ? { validUntil: options.validUntil }
330
- : {}),
331
- ...(options.assets !== undefined
332
- ? { assets: assets.get(chain.chainNamespace) ?? [] }
358
+ validUntil,
359
+ ...(options.assets !== undefined || chain.defaultTokens
360
+ ? { assets: bridgeAssets }
333
361
  : {}),
334
- ...((options.maxGasPerCall ?? chain.maxGasPerCall) !== undefined
335
- ? {
336
- maxGasPerCall: cap(options.maxGasPerCall ?? chain.maxGasPerCall, 'maxGasPerCall'),
337
- }
362
+ ...(maxGasPerCall !== undefined
363
+ ? { maxGasPerCall: cap(maxGasPerCall, 'maxGasPerCall') }
338
364
  : {}),
339
365
  };
340
366
  });
@@ -347,7 +373,7 @@ function buildPresetApi(catalog) {
347
373
  };
348
374
  },
349
375
  };
350
- // The overload narrows templates only when both required fields were supplied and validated.
376
+ // Expiry is generated; the overload narrows templates when the agent is supplied.
351
377
  return Object.freeze(api);
352
378
  }
353
379
  exports.agenticPresets = buildPresetApi(catalog_1.PRESET_CATALOG);
@@ -0,0 +1,9 @@
1
+ import { PUSH_NETWORK } from '../../constants/enums';
2
+ import type { RuleAsset } from '../agentic.types';
3
+ import type { PresetChainReadiness, PresetConfigurationIssue } from './types';
4
+ /** Offline registry checks only. Grant preparation still verifies source metadata
5
+ * and wiring against chain state before any signature or transaction. */
6
+ export declare function presetRoutes(chain: string, assets: RuleAsset[], network: PUSH_NETWORK, funding: 'push' | 'destination'): {
7
+ bridgeAssets: PresetChainReadiness['bridgeAssets'];
8
+ issues: PresetConfigurationIssue[];
9
+ };
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.presetRoutes = presetRoutes;
4
+ const viem_1 = require("viem");
5
+ const enums_1 = require("../../constants/enums");
6
+ const helpers_1 = require("../../orchestrator/internals/helpers");
7
+ const prc20_address_1 = require("../../universal/prc20-address");
8
+ const deployments_1 = require("../deployments");
9
+ const operation_limits_1 = require("./operation-limits");
10
+ /** Offline registry checks only. Grant preparation still verifies source metadata
11
+ * and wiring against chain state before any signature or transaction. */
12
+ function presetRoutes(chain, assets, network, funding) {
13
+ const issues = [];
14
+ network =
15
+ network === enums_1.PUSH_NETWORK.TESTNET ? enums_1.PUSH_NETWORK.TESTNET_DONUT : network;
16
+ if (!(0, deployments_1.generationsFor)(network).some((g) => g.advertised))
17
+ issues.push({
18
+ code: 'AGW_NETWORK_UNAVAILABLE',
19
+ message: `No verified AGW deployment is registered for ${network}.`,
20
+ });
21
+ const supported = helpers_1.SUPPORTED_GATEWAY_CHAINS.includes(chain);
22
+ if (!supported)
23
+ issues.push({
24
+ code: 'DESTINATION_UNAVAILABLE',
25
+ message: `The SDK has no supported gateway route to ${chain}; this is a catalog template.`,
26
+ });
27
+ const bridgeAssets = assets.map((asset) => {
28
+ const token = (0, operation_limits_1.assetAddress)(asset);
29
+ try {
30
+ if (!supported)
31
+ throw new Error(`No supported gateway route to ${chain}`);
32
+ const prc20 = (0, prc20_address_1.getPRC20Address)({ chain, address: token }, { network });
33
+ return {
34
+ token,
35
+ prc20: (0, viem_1.getAddress)(prc20.address),
36
+ status: 'registered',
37
+ };
38
+ }
39
+ catch {
40
+ if (funding === 'push')
41
+ issues.push({
42
+ code: 'BRIDGE_ASSET_UNAVAILABLE',
43
+ token,
44
+ message: `No SDK PRC20 mapping for ${chain}:${token} on ${network}. Use a supported source asset or explicitly choose destination funding for assets already at the CEA.`,
45
+ });
46
+ return { token, status: 'unavailable' };
47
+ }
48
+ });
49
+ return { bridgeAssets, issues };
50
+ }
51
+ //# sourceMappingURL=routes.js.map
@@ -1,4 +1,5 @@
1
1
  import type { Address } from 'viem';
2
+ import type { PUSH_NETWORK } from '../../constants/enums';
2
3
  import type { Rule, RuleAsset, RuleCall, ForeignChainNamespace } from '../agentic.types';
3
4
  export type PresetCategory = 'DEXES' | 'LENDING' | 'STAKING' | 'PREDICTION';
4
5
  export interface Preset {
@@ -7,7 +8,7 @@ export interface Preset {
7
8
  name: string;
8
9
  chains: ForeignChainNamespace[];
9
10
  }
10
- /** An editable review-screen template; both identity and expiry are required by create. */
11
+ /** Presets generate expiry; optional fields retain editable-template compatibility. */
11
12
  export type PresetRuleTemplate = Omit<Rule, 'agent' | 'validUntil'> & {
12
13
  agent?: Address;
13
14
  validUntil?: number;
@@ -22,23 +23,33 @@ export interface PresetAsset {
22
23
  maxTotal?: bigint;
23
24
  }
24
25
  export interface PresetOptions {
25
- /** Explicit Push-PC gas budget per outbound; omitted = zero. */
26
+ /** Offline route/deployment checks; defaults to TESTNET_DONUT. */
27
+ network?: PUSH_NETWORK;
28
+ /** Push funding requires PRC20 routes. Destination funding explicitly emits
29
+ * empty bridge assets while preserving selected token/call limits in metadata.
30
+ * The CEA must already hold the required destination assets. Default: push. */
31
+ funding?: 'push' | 'destination';
32
+ /** Push-PC budget ceiling per outbound; omitted uses the catalog policy (20 PC).
33
+ * Explicit zero is preserved. This is not the fee charged. */
26
34
  maxGasPerCall?: bigint;
27
35
  chainNamespace?: ForeignChainNamespace;
28
36
  agent?: Address;
37
+ /** Omitted: seven days from generation time. Explicit expiry is preserved. */
29
38
  validUntil?: number;
30
- /** DEX swaps with fewer than two tokens are zero-input-cap review templates.
31
- * Call get again with a complete pair to generate token pins and amount limits. */
39
+ /** Omitted: include the catalog's curated tokens and explicit spending policy.
40
+ * An explicit array replaces all defaults (including [] to select no tokens).
41
+ * Supplied assets retain zero for omitted caps; no policy is guessed for them.
42
+ * DEX swaps with fewer than two tokens remain zero-input-cap review templates. */
32
43
  assets?: PresetAsset[];
33
44
  }
34
45
  export interface ReadyPresetOptions extends PresetOptions {
35
46
  agent: Address;
36
- validUntil: number;
37
47
  }
38
- /** Offline configuration only: does not check deployment, reserves, balances or liquidity. */
48
+ /** Offline configuration/registry checks; chain state and balances are not checked. */
39
49
  export interface PresetConfigurationIssue {
40
- code: 'AGENT_REQUIRED' | 'EXPIRY_REQUIRED' | 'GAS_BUDGET_REQUIRED' | 'ASSETS_REQUIRED' | 'TOKEN_PAIR_REQUIRED' | 'NO_ENABLED_OPERATION';
50
+ code: 'AGENT_REQUIRED' | 'EXPIRY_REQUIRED' | 'GAS_BUDGET_REQUIRED' | 'ASSETS_REQUIRED' | 'TOKEN_PAIR_REQUIRED' | 'NO_ENABLED_OPERATION' | 'AGW_NETWORK_UNAVAILABLE' | 'DESTINATION_UNAVAILABLE' | 'BRIDGE_ASSET_UNAVAILABLE';
41
51
  message: string;
52
+ token?: string;
42
53
  }
43
54
  export interface PresetCallIssue {
44
55
  target: string;
@@ -51,12 +62,22 @@ export interface PresetCallIssue {
51
62
  export interface PresetChainReadiness {
52
63
  chainNamespace: ForeignChainNamespace;
53
64
  ready: boolean;
65
+ configurationReady: boolean;
66
+ funding: 'push' | 'destination';
67
+ /** Selected destination permissions, retained even when bridge assets is empty. */
68
+ selectedAssets: RuleAsset[];
69
+ /** Registry presence only; grant preparation verifies exact on-chain identity. */
70
+ bridgeAssets: {
71
+ token: string;
72
+ status: 'registered' | 'unavailable';
73
+ prc20?: Address;
74
+ }[];
54
75
  issues: PresetConfigurationIssue[];
55
76
  /** Calls present in the rule but disabled by a zero amount/value maximum. */
56
77
  disabledCalls: PresetCallIssue[];
57
78
  /** Calls deliberately omitted, including operations with incompatible amount units. */
58
79
  excludedCalls: PresetCallIssue[];
59
- /** Known fixed-token/reserve addresses in this catalog, not selected defaults. */
80
+ /** Known catalog token addresses, including the curated default subset. */
60
81
  knownTokens: string[];
61
82
  /** Selected tokens with a zero Push-outbound budget; destination-held assets are separate. */
62
83
  zeroBridgeLimits: string[];
@@ -4,6 +4,10 @@ exports.getPRC20Address = getPRC20Address;
4
4
  const enums_1 = require("../constants/enums");
5
5
  const tokens_1 = require("../constants/tokens");
6
6
  const chain_1 = require("../constants/chain");
7
+ const viem_1 = require("viem");
8
+ const sameTokenAddress = (a, b) => (0, viem_1.isAddress)(a, { strict: false }) && (0, viem_1.isAddress)(b, { strict: false })
9
+ ? a.toLowerCase() === b.toLowerCase()
10
+ : a === b;
7
11
  /** Shared pure origin-to-PRC20 mapping; no signer or network reads. */
8
12
  function getPRC20Address(token, options) {
9
13
  const network = options?.network ?? enums_1.PUSH_NETWORK.TESTNET_DONUT;
@@ -33,7 +37,8 @@ function getPRC20Address(token, options) {
33
37
  // MoveableToken path: infer chain by symbol + address
34
38
  for (const [key, list] of Object.entries(tokens_1.MOVEABLE_TOKENS)) {
35
39
  const k = key;
36
- const match = (list ?? []).find((t) => t.symbol === token.symbol && t.address === token.address);
40
+ const match = (list ?? []).find((t) => t.symbol === token.symbol &&
41
+ sameTokenAddress(t.address, token.address));
37
42
  if (match) {
38
43
  originChain = k;
39
44
  tokenSymbol = match.symbol;
@@ -46,7 +51,7 @@ function getPRC20Address(token, options) {
46
51
  // { chain, address } path: trust the provided chain and resolve symbol via registry
47
52
  originChain = token.chain;
48
53
  const list = tokens_1.MOVEABLE_TOKENS[originChain] ?? [];
49
- const match = list.find((t) => t.address === token.address);
54
+ const match = list.find((t) => sameTokenAddress(t.address, token.address));
50
55
  if (match) {
51
56
  tokenSymbol = match.symbol;
52
57
  tokenDecimals = match.decimals;
@@ -60,14 +65,23 @@ function getPRC20Address(token, options) {
60
65
  throw new Error(`No PRC20 address map configured for network: ${network}`);
61
66
  }
62
67
  // Map token → synthetic key by origin chain family
63
- const isEthFamily = originChain === enums_1.CHAIN.ETHEREUM_MAINNET ||
64
- originChain === enums_1.CHAIN.ETHEREUM_SEPOLIA;
68
+ // Current synthetic keys represent testnet sources. Mainnet assets must never
69
+ // resolve to a same-symbol Sepolia token on Donut.
70
+ const isEthFamily = originChain === enums_1.CHAIN.ETHEREUM_SEPOLIA;
65
71
  const isArbFamily = originChain === enums_1.CHAIN.ARBITRUM_SEPOLIA;
66
72
  const isBaseFamily = originChain === enums_1.CHAIN.BASE_SEPOLIA;
67
73
  const isBnbFamily = originChain === enums_1.CHAIN.BNB_TESTNET;
68
74
  const isSolFamily = originChain === enums_1.CHAIN.SOLANA_DEVNET;
69
75
  let key;
70
76
  switch (tokenSymbol) {
77
+ case 'WETH':
78
+ case 'stETH': {
79
+ // These deployments represent Sepolia tokens, not other chains' assets.
80
+ if (originChain !== enums_1.CHAIN.ETHEREUM_SEPOLIA)
81
+ throw new Error(`Unsupported ${tokenSymbol} origin chain for synthetic mapping`);
82
+ key = tokenSymbol === 'WETH' ? 'WETH_ETH' : 'stETH_ETH';
83
+ break;
84
+ }
71
85
  case 'ETH': {
72
86
  if (isEthFamily)
73
87
  key = 'pETH';