@pushchain/core 6.0.18 → 6.0.19

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/chain.d.ts +16 -0
  4. package/src/lib/constants/chain.js +32 -4
  5. package/src/lib/formatters.d.ts +1 -0
  6. package/src/lib/formatters.js +66 -1
  7. package/src/lib/orchestrator/internals/cascade.js +64 -46
  8. package/src/lib/orchestrator/internals/confirmation.js +8 -8
  9. package/src/lib/orchestrator/internals/error-decoder.d.ts +1 -0
  10. package/src/lib/orchestrator/internals/error-decoder.js +10 -0
  11. package/src/lib/orchestrator/internals/errors.js +1 -1
  12. package/src/lib/orchestrator/internals/execute-funds-only.js +8 -6
  13. package/src/lib/orchestrator/internals/execute-funds-payload.js +6 -4
  14. package/src/lib/orchestrator/internals/execute-standard.js +10 -8
  15. package/src/lib/orchestrator/internals/gateway-client.js +34 -1
  16. package/src/lib/orchestrator/internals/inbound-tracker.js +4 -3
  17. package/src/lib/orchestrator/internals/progress-route-hooks.d.ts +8 -4
  18. package/src/lib/orchestrator/internals/progress-route-hooks.js +8 -8
  19. package/src/lib/orchestrator/internals/push-chain-tx.js +71 -15
  20. package/src/lib/orchestrator/internals/response-builder.js +25 -16
  21. package/src/lib/orchestrator/internals/tx-transformer.js +11 -6
  22. package/src/lib/orchestrator/orchestrator.js +3 -17
  23. package/src/lib/orchestrator/orchestrator.types.d.ts +7 -0
  24. package/src/lib/progress-hook/progress-hook.js +37 -40
  25. package/src/lib/push-client/push-client.d.ts +39 -0
  26. package/src/lib/push-client/push-client.js +130 -12
  27. package/src/lib/universal/signer/signer.d.ts +1 -1
  28. package/src/lib/universal/signer/signer.js +83 -3
  29. package/src/lib/universal/universal.types.d.ts +37 -0
  30. package/src/lib/vm-client/evm-client.d.ts +43 -0
  31. package/src/lib/vm-client/evm-client.js +154 -1
@@ -21,10 +21,75 @@ const bs58_1 = require("../../internal/bs58");
21
21
  /**
22
22
  * Converts viem's parsed transaction to an ethers-compatible TransactionRequest.
23
23
  * viem uses `gas` while ethers expects `gasLimit`.
24
+ *
25
+ * For EIP-7702 (type-4) transactions the `authorizationList` entries also need
26
+ * reshaping: viem represents the signature inline (`r`/`s`/`yParity`), whereas
27
+ * ethers expects a nested `signature`. Without this conversion ethers normalises
28
+ * the entry to a zero signature and the delegation is silently dropped.
24
29
  */
25
30
  function toEthersTxRequest(viemTx) {
26
- const _a = viemTx, { gas, type } = _a, rest = tslib_1.__rest(_a, ["gas", "type"]);
27
- return Object.assign(Object.assign({}, rest), { gasLimit: gas });
31
+ const _a = viemTx, { gas, type, authorizationList } = _a, rest = tslib_1.__rest(_a, ["gas", "type", "authorizationList"]);
32
+ const out = Object.assign(Object.assign({}, rest), { gasLimit: gas });
33
+ if (Array.isArray(authorizationList) && authorizationList.length > 0) {
34
+ out['type'] = 4;
35
+ out['authorizationList'] = authorizationList.map((a) => {
36
+ var _a;
37
+ return ({
38
+ address: (_a = a.address) !== null && _a !== void 0 ? _a : a.contractAddress,
39
+ chainId: a.chainId,
40
+ nonce: a.nonce,
41
+ signature: { r: a.r, s: a.s, yParity: a.yParity },
42
+ });
43
+ });
44
+ }
45
+ return out;
46
+ }
47
+ /**
48
+ * Builds an EIP-7702 `signAuthorization` for a viem WalletClient, but ONLY when
49
+ * the underlying account can sign offline (a local account). viem exposes the
50
+ * `signAuthorization` action on every client — including JSON-RPC accounts that
51
+ * cannot actually produce one — so we gate on `account.signAuthorization`
52
+ * (present on local accounts only). Returns `undefined` otherwise so callers
53
+ * fall back to non-7702 execution.
54
+ */
55
+ function viemSignAuthorization(wallet, address) {
56
+ const account = wallet === null || wallet === void 0 ? void 0 : wallet.account;
57
+ if (!account || typeof account.signAuthorization !== 'function') {
58
+ return undefined;
59
+ }
60
+ return (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ contractAddress, chainId, nonce, executor }) {
61
+ const auth = yield wallet.signAuthorization(Object.assign(Object.assign(Object.assign({ account: account || address, contractAddress }, (chainId !== undefined ? { chainId } : {})), (nonce !== undefined ? { nonce } : {})), (executor ? { executor } : {})));
62
+ return auth;
63
+ });
64
+ }
65
+ /**
66
+ * Builds an EIP-7702 `signAuthorization` for an ethers v6 signer. Presence of
67
+ * `authorize` is not a reliable capability signal (AbstractSigner's default
68
+ * throws), so callers MUST also handle a runtime failure and fall back — see
69
+ * `EIP7702NotSupportedError` in evm-client. Returns `undefined` when the method
70
+ * is entirely absent (e.g. ethers v5).
71
+ */
72
+ function ethersSignAuthorization(wallet, chain) {
73
+ if (typeof (wallet === null || wallet === void 0 ? void 0 : wallet.authorize) !== 'function')
74
+ return undefined;
75
+ return (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ contractAddress, chainId, nonce }) {
76
+ var _b, _c, _d;
77
+ const cid = chainId !== null && chainId !== void 0 ? chainId : Number(chain.split(':')[1]);
78
+ const auth = yield wallet.authorize({
79
+ address: contractAddress,
80
+ chainId: cid,
81
+ nonce: nonce !== null && nonce !== void 0 ? nonce : 0,
82
+ });
83
+ const sig = auth.signature;
84
+ return {
85
+ address: contractAddress,
86
+ chainId: Number((_b = auth.chainId) !== null && _b !== void 0 ? _b : cid),
87
+ nonce: Number((_d = (_c = auth.nonce) !== null && _c !== void 0 ? _c : nonce) !== null && _d !== void 0 ? _d : 0),
88
+ r: sig.r,
89
+ s: sig.s,
90
+ yParity: Number(sig.yParity),
91
+ };
92
+ });
28
93
  }
29
94
  /**
30
95
  * Maps an EVM numeric chainId to the corresponding CHAIN enum.
@@ -72,12 +137,13 @@ function chainIdToChain(chainId) {
72
137
  * signAndSendTransaction: async (data) => signRawTx(data),
73
138
  * });
74
139
  */
75
- function createUniversalSigner({ account, signMessage, signAndSendTransaction, signTypedData, }) {
140
+ function createUniversalSigner({ account, signMessage, signAndSendTransaction, signTypedData, signAuthorization, }) {
76
141
  return {
77
142
  account,
78
143
  signMessage,
79
144
  signAndSendTransaction,
80
145
  signTypedData,
146
+ signAuthorization,
81
147
  };
82
148
  }
83
149
  /**
@@ -93,6 +159,10 @@ function toUniversalFromKeypair(clientOrAccount_1, _a) {
93
159
  let signMessage;
94
160
  let signAndSendTransaction;
95
161
  let signTypedData;
162
+ // Optional: only set for EVM wallets that support EIP-7702 authorization
163
+ // signing. Left undefined for Solana and wallets without 7702 support, in
164
+ // which case callers fall back to a non-7702 path.
165
+ let signAuthorization;
96
166
  // Check if signer has UID='custom', then we take signMessage, signAndSendTransaction, signTypedData, chain and address from the CustomUniversalSigner.
97
167
  // If ViemSigner, convert ViemSigner to UniversalSigner.
98
168
  switch (library) {
@@ -129,6 +199,9 @@ function toUniversalFromKeypair(clientOrAccount_1, _a) {
129
199
  const sigHex = yield wallet.signTypedData(domain, types, message);
130
200
  return (0, viem_1.hexToBytes)(sigHex);
131
201
  });
202
+ // EIP-7702 authorization signing (ethers v6). Backed by a runtime
203
+ // fallback in evm-client since `authorize` presence isn't a guarantee.
204
+ signAuthorization = ethersSignAuthorization(wallet, chain);
132
205
  break;
133
206
  }
134
207
  case enums_1.LIBRARY.ETHEREUM_VIEM: {
@@ -157,6 +230,9 @@ function toUniversalFromKeypair(clientOrAccount_1, _a) {
157
230
  });
158
231
  return (0, viem_1.hexToBytes)(hexSig);
159
232
  });
233
+ // EIP-7702 authorization signing — only for local viem accounts that can
234
+ // sign offline (gated inside the helper); JSON-RPC accounts fall back.
235
+ signAuthorization = viemSignAuthorization(wallet, address);
160
236
  break;
161
237
  }
162
238
  case enums_1.LIBRARY.SOLANA_WEB3JS: {
@@ -225,6 +301,7 @@ function toUniversalFromKeypair(clientOrAccount_1, _a) {
225
301
  signMessage,
226
302
  signAndSendTransaction,
227
303
  signTypedData,
304
+ signAuthorization,
228
305
  };
229
306
  return createUniversalSigner(universalSigner);
230
307
  });
@@ -320,6 +397,7 @@ function generateSkeletonFromEthersV6(signer) {
320
397
  const sigHex = yield signer.signTypedData(domain, types, message);
321
398
  return (0, viem_1.hexToBytes)(sigHex);
322
399
  }),
400
+ signAuthorization: ethersSignAuthorization(signer, chain),
323
401
  };
324
402
  });
325
403
  }
@@ -369,6 +447,8 @@ function generateSkeletonFromViem(signer) {
369
447
  });
370
448
  return (0, viem_1.hexToBytes)(hexSig);
371
449
  }),
450
+ // Gated on a local account inside the helper (JSON-RPC accounts → undefined).
451
+ signAuthorization: viemSignAuthorization(signer, address),
372
452
  };
373
453
  });
374
454
  }
@@ -48,6 +48,35 @@ export interface EthersV6SignerType {
48
48
  signTypedData: (domain: TypedDataDomain, types: Record<string, Array<any>>, value: Record<string, any>) => Promise<string>;
49
49
  provider?: any;
50
50
  }
51
+ /**
52
+ * Parameters for signing an EIP-7702 authorization (delegating an EOA's code to
53
+ * `contractAddress`).
54
+ */
55
+ export type SignAuthorizationParams = {
56
+ /** Address of the contract to delegate the EOA's code to. */
57
+ contractAddress: `0x${string}`;
58
+ /** Chain id for the delegation. Defaults to the signer's chain. */
59
+ chainId?: number;
60
+ /** Authorization nonce. Omit to let the wallet fill it. */
61
+ nonce?: number;
62
+ /**
63
+ * Set `'self'` when the signing EOA also submits the type-4 transaction; the
64
+ * wallet then bumps the authorization nonce by one. Prefer passing an explicit
65
+ * `nonce` for deterministic self-execution.
66
+ */
67
+ executor?: 'self';
68
+ };
69
+ /** A signed EIP-7702 authorization (viem-compatible shape). */
70
+ export type SignedAuthorization = {
71
+ /** Address of the contract the EOA's code is delegated to. */
72
+ address: `0x${string}`;
73
+ chainId: number;
74
+ nonce: number;
75
+ r: `0x${string}`;
76
+ s: `0x${string}`;
77
+ yParity: number;
78
+ v?: bigint;
79
+ };
51
80
  /**
52
81
  * A signer capable of signing messages for a specific chain.
53
82
  * Used to abstract away signing across multiple VM types.
@@ -84,12 +113,20 @@ export interface UniversalSigner {
84
113
  * Used for sending on-chain transactions.
85
114
  */
86
115
  signAndSendTransaction: (unsignedTx: Uint8Array) => Promise<Uint8Array>;
116
+ /**
117
+ * Signs an EIP-7702 authorization, delegating the EOA's code to
118
+ * `contractAddress`. Optional — only EVM signers whose underlying wallet
119
+ * supports EIP-7702 implement it. When absent, callers must fall back to a
120
+ * non-7702 path (e.g. sequential single calls).
121
+ */
122
+ signAuthorization?: (params: SignAuthorizationParams) => Promise<SignedAuthorization>;
87
123
  }
88
124
  export interface UniversalSignerSkeleton {
89
125
  signerId: string;
90
126
  account: UniversalAccount;
91
127
  signMessage: (data: Uint8Array) => Promise<Uint8Array>;
92
128
  signAndSendTransaction: (unsignedTx: Uint8Array) => Promise<Uint8Array>;
129
+ signAuthorization?: (params: SignAuthorizationParams) => Promise<SignedAuthorization>;
93
130
  signTypedData?: ({ domain, types, primaryType, message, }: {
94
131
  domain: TypedDataDomain;
95
132
  types: TypedData;
@@ -1,6 +1,25 @@
1
1
  import { UniversalSigner } from '../universal/universal.types';
2
2
  import { ClientOptions, ReadContractParams, TxResponse, WriteContractParams } from './vm-client.types';
3
3
  import { PublicClient, Hex } from 'viem';
4
+ /** A single call in a 7702 batch. */
5
+ export type BatchCall = {
6
+ to: `0x${string}`;
7
+ value: bigint;
8
+ data: `0x${string}`;
9
+ };
10
+ /**
11
+ * Thrown by {@link EvmClient.sendBatch7702} when the signer cannot actually
12
+ * produce an EIP-7702 authorization — either the capability is absent, or the
13
+ * wallet exposes a `signAuthorization`/`authorize` method that throws at call
14
+ * time (e.g. viem JSON-RPC accounts, ethers' default `AbstractSigner.authorize`).
15
+ *
16
+ * It is raised strictly BEFORE any transaction is broadcast, so callers may
17
+ * safely fall back to a non-7702 path without risk of double execution.
18
+ */
19
+ export declare class EIP7702NotSupportedError extends Error {
20
+ readonly reason?: unknown;
21
+ constructor(reason?: unknown);
22
+ }
4
23
  /**
5
24
  * EVM client for reading and writing to Ethereum-compatible chains
6
25
  *
@@ -126,6 +145,30 @@ export declare class EvmClient {
126
145
  nonce?: number;
127
146
  gas?: bigint;
128
147
  }): Promise<Hex>;
148
+ /**
149
+ * Executes a batch of calls atomically from a native EOA using EIP-7702.
150
+ *
151
+ * The EOA signs an authorization delegating its code to `executor`
152
+ * (`PushBatchExecutor`, an ERC-7821 executor) and submits a single type-4
153
+ * transaction calling `execute(mode, executionData)` on itself. Because the
154
+ * authority equals the transaction sender, ERC-7821's self-call check
155
+ * authorizes the batch — no per-call signature is needed. All calls succeed
156
+ * or the whole tx reverts.
157
+ *
158
+ * Requires `signer.signAuthorization` (EIP-7702 capable wallet). Callers
159
+ * should check for it and fall back to sequential execution when absent.
160
+ *
161
+ * Rejects a zero `to` address: ERC-7579/7821 reinterprets `target == 0` as
162
+ * `address(this)`, which would silently differ from the sequential fallback
163
+ * (which sends to the zero address). Disallow it so behaviour is consistent.
164
+ *
165
+ * @returns The transaction hash.
166
+ */
167
+ sendBatch7702({ executor, calls, signer, }: {
168
+ executor: `0x${string}`;
169
+ calls: BatchCall[];
170
+ signer: UniversalSigner;
171
+ }): Promise<Hex>;
129
172
  /**
130
173
  * Estimates the gas required for a transaction.
131
174
  *
@@ -1,8 +1,52 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EvmClient = void 0;
3
+ exports.EvmClient = exports.EIP7702NotSupportedError = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const viem_1 = require("viem");
6
+ /**
7
+ * ABI of the ERC-7821 `execute(bytes32 mode, bytes executionData)` entrypoint —
8
+ * invoked by a native Push EOA on itself after it delegates its code to the
9
+ * executor (`PushBatchExecutor is ERC7821`) via EIP-7702. ERC-7821's default
10
+ * authorization (`caller == address(this)`) is exactly that self-call.
11
+ */
12
+ const PUSH_BATCH_EXECUTOR_ABI = (0, viem_1.parseAbi)([
13
+ 'function execute(bytes32 mode, bytes executionData) payable',
14
+ ]);
15
+ /**
16
+ * ERC-7821 single-batch execution mode: callType `0x01` (batch) in the top byte,
17
+ * default exec type and zero mode-selector — i.e. `0x01` followed by 31 zero
18
+ * bytes. This is the only mode the executor supports.
19
+ */
20
+ const ERC7821_BATCH_MODE = '0x0100000000000000000000000000000000000000000000000000000000000000';
21
+ /**
22
+ * ABI tuple for an ERC-7821 / ERC-7579 `Execution`. `executionData` for a batch
23
+ * is `abi.encode(Execution[])`.
24
+ */
25
+ const ERC7821_EXECUTION_TUPLE = {
26
+ type: 'tuple[]',
27
+ components: [
28
+ { name: 'target', type: 'address' },
29
+ { name: 'value', type: 'uint256' },
30
+ { name: 'callData', type: 'bytes' },
31
+ ],
32
+ };
33
+ /**
34
+ * Thrown by {@link EvmClient.sendBatch7702} when the signer cannot actually
35
+ * produce an EIP-7702 authorization — either the capability is absent, or the
36
+ * wallet exposes a `signAuthorization`/`authorize` method that throws at call
37
+ * time (e.g. viem JSON-RPC accounts, ethers' default `AbstractSigner.authorize`).
38
+ *
39
+ * It is raised strictly BEFORE any transaction is broadcast, so callers may
40
+ * safely fall back to a non-7702 path without risk of double execution.
41
+ */
42
+ class EIP7702NotSupportedError extends Error {
43
+ constructor(reason) {
44
+ super('Signer cannot produce an EIP-7702 authorization');
45
+ this.name = 'EIP7702NotSupportedError';
46
+ this.reason = reason;
47
+ }
48
+ }
49
+ exports.EIP7702NotSupportedError = EIP7702NotSupportedError;
6
50
  /**
7
51
  * EVM client for reading and writing to Ethereum-compatible chains
8
52
  *
@@ -203,6 +247,115 @@ class EvmClient {
203
247
  return (0, viem_1.bytesToHex)(txHashBytes);
204
248
  });
205
249
  }
250
+ /**
251
+ * Executes a batch of calls atomically from a native EOA using EIP-7702.
252
+ *
253
+ * The EOA signs an authorization delegating its code to `executor`
254
+ * (`PushBatchExecutor`, an ERC-7821 executor) and submits a single type-4
255
+ * transaction calling `execute(mode, executionData)` on itself. Because the
256
+ * authority equals the transaction sender, ERC-7821's self-call check
257
+ * authorizes the batch — no per-call signature is needed. All calls succeed
258
+ * or the whole tx reverts.
259
+ *
260
+ * Requires `signer.signAuthorization` (EIP-7702 capable wallet). Callers
261
+ * should check for it and fall back to sequential execution when absent.
262
+ *
263
+ * Rejects a zero `to` address: ERC-7579/7821 reinterprets `target == 0` as
264
+ * `address(this)`, which would silently differ from the sequential fallback
265
+ * (which sends to the zero address). Disallow it so behaviour is consistent.
266
+ *
267
+ * @returns The transaction hash.
268
+ */
269
+ sendBatch7702(_a) {
270
+ return tslib_1.__awaiter(this, arguments, void 0, function* ({ executor, calls, signer, }) {
271
+ if (!signer.signAuthorization) {
272
+ // No capability at all → safe pre-broadcast signal to fall back.
273
+ throw new EIP7702NotSupportedError();
274
+ }
275
+ if (!signer.signAndSendTransaction) {
276
+ throw new Error('signer.signAndSendTransaction is undefined');
277
+ }
278
+ const account = signer.account.address;
279
+ // ERC-7821 maps target == address(0) to address(this); reject it so a batch
280
+ // can't silently behave differently here vs. the sequential fallback.
281
+ if (calls.some((c) => /^0x0{40}$/i.test(c.to) || c.to.toLowerCase() === '0x0')) {
282
+ throw new Error('sendBatch7702: a call has a zero `to` address, which ERC-7821 reinterprets ' +
283
+ 'as the account itself. Use an explicit target.');
284
+ }
285
+ // ERC-7821: execute(mode, executionData) where executionData = abi.encode(Execution[]).
286
+ const executionData = (0, viem_1.encodeAbiParameters)([ERC7821_EXECUTION_TUPLE], [
287
+ calls.map((c) => ({ target: c.to, value: c.value, callData: c.data })),
288
+ ]);
289
+ const data = (0, viem_1.encodeFunctionData)({
290
+ abi: PUSH_BATCH_EXECUTOR_ABI,
291
+ functionName: 'execute',
292
+ args: [ERC7821_BATCH_MODE, executionData],
293
+ });
294
+ const [feePerGas, chainId, nonce] = yield Promise.all([
295
+ this.publicClient.estimateFeesPerGas(),
296
+ this.publicClient.getChainId(),
297
+ this.publicClient.getTransactionCount({
298
+ address: account,
299
+ blockTag: 'pending',
300
+ }),
301
+ ]);
302
+ // EIP-7702 self-execution: the transaction consumes `nonce`, so the
303
+ // authorization (validated after the sender's nonce is bumped) must carry
304
+ // `nonce + 1`. This is the only step that can reveal the wallet truly can't
305
+ // sign a 7702 authorization (presence of the method is not a guarantee), so
306
+ // a failure here is surfaced as EIP7702NotSupportedError — pre-broadcast,
307
+ // hence safe for the caller to fall back on.
308
+ let authorization;
309
+ try {
310
+ authorization = yield signer.signAuthorization({
311
+ contractAddress: executor,
312
+ chainId,
313
+ nonce: nonce + 1,
314
+ });
315
+ }
316
+ catch (err) {
317
+ throw new EIP7702NotSupportedError(err);
318
+ }
319
+ // Estimate gas against the delegated account: override the EOA's code with
320
+ // the 7702 delegation designator (0xef0100 || executor) so estimation runs
321
+ // the executor. Fall back to a safe ceiling if the node rejects overrides.
322
+ let gas;
323
+ try {
324
+ gas = yield this.publicClient.estimateGas({
325
+ account,
326
+ to: account,
327
+ data,
328
+ stateOverride: [
329
+ {
330
+ address: account,
331
+ code: ('0xef0100' + executor.slice(2)),
332
+ },
333
+ ],
334
+ });
335
+ gas = (gas * BigInt(120)) / BigInt(100); // headroom for delegation + auth
336
+ }
337
+ catch (_b) {
338
+ // Estimation unavailable (e.g. node rejects state overrides). Scale the
339
+ // ceiling with the batch size so large batches don't OOG — the legacy
340
+ // per-call loop budgeted ~500k gas per call; mirror that plus overhead.
341
+ gas = BigInt(500000) * BigInt(calls.length) + BigInt(100000);
342
+ }
343
+ const unsignedTx = (0, viem_1.serializeTransaction)({
344
+ chainId,
345
+ type: 'eip7702',
346
+ to: account,
347
+ data,
348
+ gas,
349
+ nonce,
350
+ maxFeePerGas: feePerGas.maxFeePerGas,
351
+ maxPriorityFeePerGas: feePerGas.maxPriorityFeePerGas,
352
+ value: BigInt(0),
353
+ authorizationList: [authorization],
354
+ });
355
+ const txHashBytes = yield signer.signAndSendTransaction((0, viem_1.hexToBytes)(unsignedTx));
356
+ return (0, viem_1.bytesToHex)(txHashBytes);
357
+ });
358
+ }
206
359
  /**
207
360
  * Estimates the gas required for a transaction.
208
361
  *