@pafi-dev/core 0.3.0-beta.5 → 0.3.0-beta.7

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/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { erc20Abi, issuerRegistryAbi, mintingOracleAbi, permit2Abi, pointTokenAb
5
5
  export { buildBurnRequestTypedData, buildDomain, buildMintRequestTypedData, buildReceiverConsentTypedData, signBurnRequest, signMintRequest, signReceiverConsent, verifyBurnRequest, verifyMintRequest, verifyReceiverConsent } from './eip712/index.js';
6
6
  export { getIssuer, getMintRequestNonce, getPointTokenBalance, getPointTokenIssuer, getPointTokenIssuerAddress, getReceiverConsentNonce, getTokenName, isActiveIssuer, isMinter, verifyMintCap } from './contract/index.js';
7
7
  export { buildAllPaths, combineRoutes, findBestQuote, quoteBestRoute, quoteExactInput, quoteExactInputSingle } from './quoting/index.js';
8
- import { O as Operation, P as PartialUserOperation, U as UserOperation, S as SwapSimulationResult } from './index-B06IJlHe.js';
8
+ import { P as PartialUserOperation, O as Operation, U as UserOperation, S as SwapSimulationResult } from './index-B06IJlHe.js';
9
9
  export { B as BuildSwapWithGasDeductionParams, E as ENTRY_POINT_V07, a as PaymasterFields, b as SETTLE_ALL, c as SWAP_EXACT_IN, T as TAKE_ALL, d as UserOpReceipt, V as V4_SWAP, Z as ZERO_VALUE, e as buildErc20ApprovalCalldata, f as buildPermit2ApprovalCalldata, g as buildSwapFromQuote, h as buildSwapWithGasDeduction, i as buildUniversalRouterExecuteArgs, j as buildV4SwapInput, k as checkAllowance, s as simulateSwap } from './index-B06IJlHe.js';
10
10
  import { LoginMessageParams } from './auth/index.js';
11
11
  export { VerifyLoginResult, createLoginMessage, parseLoginMessage, verifyLoginMessage } from './auth/index.js';
@@ -87,6 +87,256 @@ declare class ApiError extends PafiSDKError {
87
87
  constructor(message: string, status?: number | undefined);
88
88
  }
89
89
 
90
+ /**
91
+ * Orderly Network Vault — entrypoint for **perp deposit** flow on
92
+ * Base mainnet (Scenario 3).
93
+ *
94
+ * Source of truth for addresses + integration:
95
+ * https://orderly.network/docs/build-on-omnichain/addresses#base
96
+ *
97
+ * ## Architecture
98
+ *
99
+ * User wallet (Base)
100
+ * │ 1. USDC.approve(vault, amount)
101
+ * │ 2. vault.deposit{value: layerZeroFee}({
102
+ * │ accountId, brokerHash, tokenHash, tokenAmount
103
+ * │ })
104
+ * ▼
105
+ * OrderlyVault (Base)
106
+ * │ — locks USDC, emits LayerZero message
107
+ * ▼
108
+ * Orderly chain (off-chain matching engine)
109
+ * │ — credits the user's perp account
110
+ * ▼
111
+ * User can now place perp orders via Orderly REST/WS API
112
+ *
113
+ * **Important:** `value` is required and is the LayerZero cross-chain
114
+ * fee (paid in native ETH on Base). Quote it via
115
+ * `vault.getDepositFee(user, data)` BEFORE calling deposit. Fees vary
116
+ * with destination chain congestion.
117
+ *
118
+ * ## Sponsored vs direct
119
+ *
120
+ * Coinbase Paymaster sponsors GAS, not `msg.value`. So even on the
121
+ * sponsored path the user MUST hold enough native ETH on Base to cover
122
+ * `getDepositFee()` (typically ~0.0002 ETH at quiet times).
123
+ */
124
+ /** Orderly Vault on Base mainnet (EIP-55 checksum). */
125
+ declare const ORDERLY_VAULT_BASE_MAINNET: Address;
126
+ /**
127
+ * Per-chain registry of the Orderly Vault. Add chains here as Orderly
128
+ * deploys them.
129
+ */
130
+ declare const ORDERLY_VAULT_ADDRESSES: Record<number, Address>;
131
+ /**
132
+ * Pre-computed broker hashes — `keccak256(abi.encodePacked(brokerId))`.
133
+ * Add new brokers as they launch.
134
+ *
135
+ * Reference: https://orderly.network/docs/build-on-omnichain/user-flows/accountId
136
+ */
137
+ declare const BROKER_HASHES: {
138
+ /** Default partner broker on Base — most commonly whitelisted. */
139
+ readonly woofi_pro: `0x${string}`;
140
+ /** Orderly's own white-label broker. */
141
+ readonly orderly: `0x${string}`;
142
+ /** LogX. */
143
+ readonly logx: `0x${string}`;
144
+ };
145
+ /**
146
+ * Pre-computed token hashes — `keccak256(abi.encodePacked(tokenSymbol))`.
147
+ * The hash maps to the actual ERC-20 address on each chain via
148
+ * `vault.getAllowedToken(tokenHash)`.
149
+ *
150
+ * Note: Orderly canonicalises by symbol (USDC, not "USD Coin").
151
+ */
152
+ declare const TOKEN_HASHES: {
153
+ readonly USDC: `0x${string}`;
154
+ };
155
+ /**
156
+ * Compute Orderly's `accountId` — uniquely identifies a user+broker
157
+ * tuple. Matches the off-chain formula used by the Orderly SDK so
158
+ * deposits + future REST API calls reference the same account.
159
+ *
160
+ * accountId = keccak256(abi.encode(user, brokerHash))
161
+ *
162
+ * Note `abi.encode` (not `encodePacked`) — uses 32-byte left-padding.
163
+ */
164
+ declare function computeAccountId(user: Address, brokerHash: Hex): Hex;
165
+ /**
166
+ * Minimal Orderly Vault ABI — just the functions PAFI needs for
167
+ * Scenario 3 (perp deposit). Full ABI is on Orderly's docs site.
168
+ */
169
+ declare const ORDERLY_VAULT_ABI: readonly [{
170
+ readonly type: "function";
171
+ readonly name: "deposit";
172
+ readonly stateMutability: "payable";
173
+ readonly inputs: readonly [{
174
+ readonly name: "data";
175
+ readonly type: "tuple";
176
+ readonly components: readonly [{
177
+ readonly name: "accountId";
178
+ readonly type: "bytes32";
179
+ }, {
180
+ readonly name: "brokerHash";
181
+ readonly type: "bytes32";
182
+ }, {
183
+ readonly name: "tokenHash";
184
+ readonly type: "bytes32";
185
+ }, {
186
+ readonly name: "tokenAmount";
187
+ readonly type: "uint128";
188
+ }];
189
+ }];
190
+ readonly outputs: readonly [];
191
+ }, {
192
+ readonly type: "function";
193
+ readonly name: "getDepositFee";
194
+ readonly stateMutability: "view";
195
+ readonly inputs: readonly [{
196
+ readonly name: "user";
197
+ readonly type: "address";
198
+ }, {
199
+ readonly name: "data";
200
+ readonly type: "tuple";
201
+ readonly components: readonly [{
202
+ readonly name: "accountId";
203
+ readonly type: "bytes32";
204
+ }, {
205
+ readonly name: "brokerHash";
206
+ readonly type: "bytes32";
207
+ }, {
208
+ readonly name: "tokenHash";
209
+ readonly type: "bytes32";
210
+ }, {
211
+ readonly name: "tokenAmount";
212
+ readonly type: "uint128";
213
+ }];
214
+ }];
215
+ readonly outputs: readonly [{
216
+ readonly name: "";
217
+ readonly type: "uint256";
218
+ }];
219
+ }, {
220
+ readonly type: "function";
221
+ readonly name: "getAllowedBroker";
222
+ readonly stateMutability: "view";
223
+ readonly inputs: readonly [{
224
+ readonly name: "brokerHash";
225
+ readonly type: "bytes32";
226
+ }];
227
+ readonly outputs: readonly [{
228
+ readonly name: "";
229
+ readonly type: "bool";
230
+ }];
231
+ }, {
232
+ readonly type: "function";
233
+ readonly name: "getAllowedToken";
234
+ readonly stateMutability: "view";
235
+ readonly inputs: readonly [{
236
+ readonly name: "tokenHash";
237
+ readonly type: "bytes32";
238
+ }];
239
+ readonly outputs: readonly [{
240
+ readonly name: "";
241
+ readonly type: "address";
242
+ }];
243
+ }];
244
+ /** Struct mirror of `IOrderlyVault.VaultDepositFE` for type-safe builders. */
245
+ interface VaultDepositFE {
246
+ accountId: Hex;
247
+ brokerHash: Hex;
248
+ tokenHash: Hex;
249
+ /** uint128 — USDC has 6 decimals. */
250
+ tokenAmount: bigint;
251
+ }
252
+
253
+ /**
254
+ * v1.5 — Scenario 3: deposit USDC from the user's wallet into the
255
+ * Orderly perp Vault on Base mainnet.
256
+ *
257
+ * Builds a `PartialUserOperation` packaging the 2 inner calls:
258
+ *
259
+ * 1. `USDC.approve(orderlyVault, amount)` — let the Vault pull USDC
260
+ * 2. `OrderlyVault.deposit{value: layerZeroFee}(VaultDepositFE)` —
261
+ * transfer USDC into the Vault + emit a LayerZero message that
262
+ * credits the user's perp account on the Orderly chain
263
+ *
264
+ * ## ⚠️ Native ETH constraint
265
+ *
266
+ * Coinbase Paymaster sponsors GAS, **not `msg.value`**. The LayerZero
267
+ * cross-chain fee MUST come from the user's own native ETH balance on
268
+ * Base — even when the rest of the UserOp is sponsored.
269
+ *
270
+ * Quote the fee BEFORE calling this builder via:
271
+ *
272
+ * const fee = await client.readContract({
273
+ * address: orderlyVault,
274
+ * abi: ORDERLY_VAULT_ABI,
275
+ * functionName: 'getDepositFee',
276
+ * args: [user, depositData],
277
+ * });
278
+ *
279
+ * If `user.eth < fee`, surface this error to the FE so the user can
280
+ * top up before retrying — the Vault `deposit{value: 0}` call will
281
+ * revert otherwise.
282
+ *
283
+ * ## Why no PT/USDC fee deduction?
284
+ *
285
+ * Unlike Scenario 1/2/4 which deduct an operator fee in PT/USDC inside
286
+ * the batch, perp deposit doesn't append a fee transfer because:
287
+ *
288
+ * - User is paying Orderly's LayerZero fee directly via `msg.value`
289
+ * - There's no PAFI-specific operator cost on top — Orderly handles
290
+ * the off-chain accounting once the LayerZero message lands
291
+ *
292
+ * If we want to charge a PAFI service fee on top later, append a
293
+ * `USDC.transfer(feeRecipient, fee)` op — same pattern as the swap
294
+ * builder.
295
+ */
296
+ interface BuildPerpDepositWithGasDeductionParams {
297
+ /** User EOA (will be `msg.sender` via EIP-7702 delegation). */
298
+ userAddress: Address;
299
+ /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */
300
+ aaNonce: bigint;
301
+ /** Chain ID for Orderly Vault address resolution. */
302
+ chainId: number;
303
+ /**
304
+ * Override Orderly Vault address (e.g. for fork tests). Defaults to
305
+ * `ORDERLY_VAULT_ADDRESSES[chainId]`.
306
+ */
307
+ vaultAddress?: Address;
308
+ /**
309
+ * USDC ERC-20 address — the actual token Orderly accepts. Caller
310
+ * resolves this via `vault.getAllowedToken(TOKEN_HASHES.USDC)` so we
311
+ * don't hardcode the wrong USDC variant (native vs bridged).
312
+ */
313
+ usdcAddress: Address;
314
+ /** USDC amount to deposit (uint128, 6 decimals). */
315
+ amount: bigint;
316
+ /**
317
+ * Pre-built `VaultDepositFE` struct — caller computes accountId via
318
+ * `computeAccountId(user, brokerHash)` and supplies tokenHash.
319
+ */
320
+ depositData: VaultDepositFE;
321
+ /**
322
+ * LayerZero fee in wei — from `vault.getDepositFee(user, data)`.
323
+ * Becomes the `msg.value` on the `deposit()` call. User MUST hold
324
+ * ≥ this much native ETH or the call reverts.
325
+ */
326
+ layerZeroFee: bigint;
327
+ gasLimits?: {
328
+ callGasLimit?: bigint;
329
+ verificationGasLimit?: bigint;
330
+ preVerificationGas?: bigint;
331
+ };
332
+ }
333
+ /**
334
+ * Build an unsigned UserOp for Scenario 3. Returns a
335
+ * `PartialUserOperation` — caller attaches paymaster sponsorship for
336
+ * gas + the user's UserOp-hash signature, then submits to the Bundler.
337
+ */
338
+ declare function buildPerpDepositWithGasDeduction(params: BuildPerpDepositWithGasDeductionParams): PartialUserOperation;
339
+
90
340
  /**
91
341
  * Build an ERC-20 `transfer(to, amount)` operation. Used inside a batch
92
342
  * to move fee tokens from the user to the fee recipient atomically with
@@ -631,21 +881,18 @@ declare const POINT_TOKEN_ABI: readonly [{
631
881
  *
632
882
  * ## Status
633
883
  *
634
- * Per-chain addresses are **still placeholder** (`0x...DE01` / `DE02`)
635
- * pending the SC team's Phase B delivery. When Coinbase publishes a
636
- * canonical BatchExecutor, or PAFI deploys its own and publishes the
637
- * address, swap the constants below callers already import from here
638
- * so no wiring changes.
884
+ * Base mainnet: points at **Coinbase Smart Wallet v2**
885
+ * (`0x7702cb554e6bFb442cb743A7dF23154544a7176C`). Coinbase publishes
886
+ * this as the canonical EIP-7702 delegation target on Base — every
887
+ * user that wants gas sponsorship via Coinbase Paymaster delegates to
888
+ * this single contract. One contract, all issuers.
639
889
  *
640
- * The ABI (`execute(Call[])`) itself is real and stable it lives
641
- * under `../../userop/batchExecute.ts` and is re-exported below so
642
- * consumers can grab both ABI + address from one import path.
890
+ * Base Sepolia: placeholder; Sepolia is not in active use for PAFI.
643
891
  *
644
- * ## Why checksum with DEAD0001 / DEAD0002 suffix
645
- *
646
- * The placeholder pattern makes the fact-of-mockness obvious in logs
647
- * and block explorers. A request targeting `0x...DE01` is impossible
648
- * to confuse with a production contract.
892
+ * The ABI (`execute(Call[])`) is exported from
893
+ * `../../userop/batchExecute.ts` and works against the Coinbase
894
+ * contract without change Coinbase Smart Wallet v2 implements the
895
+ * same call-array signature we already use.
649
896
  */
650
897
  declare const BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA: Address;
651
898
  declare const BATCH_EXECUTOR_ADDRESS_BASE_MAINNET: Address;
@@ -653,30 +900,28 @@ declare const BATCH_EXECUTOR_ADDRESS_BASE_MAINNET: Address;
653
900
  /**
654
901
  * Per-chain deployed contract addresses — v1.4 flow.
655
902
  *
656
- * ## Status (2026-04-21)
903
+ * ## Status (2026-04-22)
904
+ *
905
+ * Base mainnet (8453): **all real**. BatchExecutor now points at the
906
+ * canonical Coinbase Smart Wallet v2 contract which PAFI reuses as the
907
+ * EIP-7702 delegation target (no per-app deploy required — one
908
+ * contract, every issuer).
657
909
  *
658
- * Base mainnet (8453): all SC-delivered and real, EXCEPT `batchExecutor`
659
- * which is still a `0x...DE02` placeholder pending the Phase B delivery.
660
910
  * Base Sepolia (84532): entire row is placeholder — not in active use.
661
911
  *
662
912
  * ## What lives where
663
913
  *
664
914
  * pointToken — live POINT instance (clone of PointToken impl)
665
- * batchExecutor — EIP-7702 delegation target (PLACEHOLDER)
915
+ * batchExecutor — EIP-7702 delegation target (Coinbase Smart Wallet v2)
666
916
  * usdt — MockERC20 used by Uniswap pools
667
917
  * issuerRegistry — registry of all issuers on this chain
668
918
  * mintingOracle — per-issuer mint cap enforcer
919
+ * pafiHook — Uniswap V4 hook enforcing the 10% PT→USDT fee
669
920
  *
670
921
  * PointTokenFactory + PointToken implementation addresses live in
671
922
  * separate exports below — they're only needed at provisioning /
672
923
  * observability time, not in the hot mint/burn path, so excluded from
673
924
  * the per-chain bundle.
674
- *
675
- * ## Swap workflow when BatchExecutor lands
676
- *
677
- * 1. Replace `batchExecutor` placeholder with real address
678
- * 2. Structure / imports don't change — callers are stable
679
- * 3. Bump core to next beta and update release notes
680
925
  */
681
926
  interface ContractAddresses {
682
927
  pointToken: Address;
@@ -684,6 +929,13 @@ interface ContractAddresses {
684
929
  usdt: Address;
685
930
  issuerRegistry: Address;
686
931
  mintingOracle: Address;
932
+ /**
933
+ * Uniswap V4 hook that enforces the 10% fee on PT→USDT swaps
934
+ * (USDT→PT is free). FE reads this to build `PoolKey.hooks` when
935
+ * constructing a swap; quoters + routers need it to resolve the
936
+ * correct pool.
937
+ */
938
+ pafiHook: Address;
687
939
  }
688
940
  declare const CONTRACT_ADDRESSES: Record<number, ContractAddresses>;
689
941
  /**
@@ -1041,4 +1293,4 @@ declare class PafiSDK {
1041
1293
  signLoginMessage(message: string): Promise<Hex>;
1042
1294
  }
1043
1295
 
1044
- export { ApiError, BATCH_EXECUTOR_ABI, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET, BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA, BestQuote, type BuildPartialUserOpParams, COMMON_POOLS, COMMON_TOKENS, CONTRACT_ADDRESSES, ChainConfig, type CheckEthAndBranchParams, ConfigurationError, type ContractAddresses, EIP712Signature, LoginMessageParams, MintRequest, type ModalOpenOptions, Operation, POINT_TOKEN_FACTORY_ADDRESSES, POINT_TOKEN_IMPL_ADDRESSES, POINT_TOKEN_POOLS, POINT_TOKEN_ABI as POINT_TOKEN_V2_ABI, PafiSDK, PafiSDKConfig, PafiSDKError, type PafiWebModalAdapter, type PafiWebModalHandle, PartialUserOperation, type PaymasterConfig, PointTokenDomainConfig, PoolKey, QuoteResult, ReceiverConsent, SUPPORTED_CHAINS, type SignatureStruct, SignatureVerification, SigningError, SimulationError, type SponsorshipScenario, type SubmissionPath, SwapSimulationResult, UNIVERSAL_ROUTER_ADDRESSES, UserOperation, V4_QUOTER_ADDRESSES, _resetPaymasterConfigForTests, assembleUserOperation, buildPartialUserOperation, burnRequestTypes, checkEthAndBranch, encodeBatchExecute, erc20ApproveOp, erc20BurnOp, erc20TransferOp, getContractAddresses, getPafiWebModalAdapter, getPaymasterConfig, isPaymasterConfigured, mintRequestTypes, openPafiWebModal, openWebPopup, rawCallOp, receiverConsentTypes, setPafiWebModalAdapter, setPaymasterConfig, webPopupAdapter };
1296
+ export { ApiError, BATCH_EXECUTOR_ABI, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET, BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA, BROKER_HASHES, BestQuote, type BuildPartialUserOpParams, type BuildPerpDepositWithGasDeductionParams, COMMON_POOLS, COMMON_TOKENS, CONTRACT_ADDRESSES, ChainConfig, type CheckEthAndBranchParams, ConfigurationError, type ContractAddresses, EIP712Signature, LoginMessageParams, MintRequest, type ModalOpenOptions, ORDERLY_VAULT_ABI, ORDERLY_VAULT_ADDRESSES, ORDERLY_VAULT_BASE_MAINNET, Operation, POINT_TOKEN_FACTORY_ADDRESSES, POINT_TOKEN_IMPL_ADDRESSES, POINT_TOKEN_POOLS, POINT_TOKEN_ABI as POINT_TOKEN_V2_ABI, PafiSDK, PafiSDKConfig, PafiSDKError, type PafiWebModalAdapter, type PafiWebModalHandle, PartialUserOperation, type PaymasterConfig, PointTokenDomainConfig, PoolKey, QuoteResult, ReceiverConsent, SUPPORTED_CHAINS, type SignatureStruct, SignatureVerification, SigningError, SimulationError, type SponsorshipScenario, type SubmissionPath, SwapSimulationResult, TOKEN_HASHES, UNIVERSAL_ROUTER_ADDRESSES, UserOperation, V4_QUOTER_ADDRESSES, type VaultDepositFE, _resetPaymasterConfigForTests, assembleUserOperation, buildPartialUserOperation, buildPerpDepositWithGasDeduction, burnRequestTypes, checkEthAndBranch, computeAccountId, encodeBatchExecute, erc20ApproveOp, erc20BurnOp, erc20TransferOp, getContractAddresses, getPafiWebModalAdapter, getPaymasterConfig, isPaymasterConfigured, mintRequestTypes, openPafiWebModal, openWebPopup, rawCallOp, receiverConsentTypes, setPafiWebModalAdapter, setPaymasterConfig, webPopupAdapter };
package/dist/index.js CHANGED
@@ -93,6 +93,146 @@ import {
93
93
  // src/index.ts
94
94
  import { createPublicClient, http } from "viem";
95
95
 
96
+ // src/perp/buildPerpDepositWithGasDeduction.ts
97
+ import { encodeFunctionData } from "viem";
98
+
99
+ // src/contracts/real/orderlyVault.ts
100
+ import { keccak256, encodePacked, encodeAbiParameters } from "viem";
101
+ var ORDERLY_VAULT_BASE_MAINNET = "0xDe5cE5DD048596e46Ff671b13317aCC3C5B59b01";
102
+ var ORDERLY_VAULT_ADDRESSES = {
103
+ 8453: ORDERLY_VAULT_BASE_MAINNET
104
+ };
105
+ var BROKER_HASHES = {
106
+ /** Default partner broker on Base — most commonly whitelisted. */
107
+ woofi_pro: keccak256(encodePacked(["string"], ["woofi_pro"])),
108
+ /** Orderly's own white-label broker. */
109
+ orderly: keccak256(encodePacked(["string"], ["orderly"])),
110
+ /** LogX. */
111
+ logx: keccak256(encodePacked(["string"], ["logx"]))
112
+ };
113
+ var TOKEN_HASHES = {
114
+ USDC: keccak256(encodePacked(["string"], ["USDC"]))
115
+ };
116
+ function computeAccountId(user, brokerHash) {
117
+ return keccak256(
118
+ encodeAbiParameters(
119
+ [
120
+ { type: "address" },
121
+ { type: "bytes32" }
122
+ ],
123
+ [user, brokerHash]
124
+ )
125
+ );
126
+ }
127
+ var ORDERLY_VAULT_ABI = [
128
+ // Deposit USDC into Orderly perp account. Emits LayerZero message
129
+ // to the Orderly chain. `msg.value` MUST equal `getDepositFee(...)`.
130
+ {
131
+ type: "function",
132
+ name: "deposit",
133
+ stateMutability: "payable",
134
+ inputs: [
135
+ {
136
+ name: "data",
137
+ type: "tuple",
138
+ components: [
139
+ { name: "accountId", type: "bytes32" },
140
+ { name: "brokerHash", type: "bytes32" },
141
+ { name: "tokenHash", type: "bytes32" },
142
+ { name: "tokenAmount", type: "uint128" }
143
+ ]
144
+ }
145
+ ],
146
+ outputs: []
147
+ },
148
+ // Quote the LayerZero fee for a deposit message. Pass the same
149
+ // VaultDepositFE struct you intend to send.
150
+ {
151
+ type: "function",
152
+ name: "getDepositFee",
153
+ stateMutability: "view",
154
+ inputs: [
155
+ { name: "user", type: "address" },
156
+ {
157
+ name: "data",
158
+ type: "tuple",
159
+ components: [
160
+ { name: "accountId", type: "bytes32" },
161
+ { name: "brokerHash", type: "bytes32" },
162
+ { name: "tokenHash", type: "bytes32" },
163
+ { name: "tokenAmount", type: "uint128" }
164
+ ]
165
+ }
166
+ ],
167
+ outputs: [{ name: "", type: "uint256" }]
168
+ },
169
+ // Pre-flight check — is this brokerId whitelisted on this Vault?
170
+ {
171
+ type: "function",
172
+ name: "getAllowedBroker",
173
+ stateMutability: "view",
174
+ inputs: [{ name: "brokerHash", type: "bytes32" }],
175
+ outputs: [{ name: "", type: "bool" }]
176
+ },
177
+ // Pre-flight check — what ERC-20 address does this token symbol
178
+ // resolve to on this Vault?
179
+ {
180
+ type: "function",
181
+ name: "getAllowedToken",
182
+ stateMutability: "view",
183
+ inputs: [{ name: "tokenHash", type: "bytes32" }],
184
+ outputs: [{ name: "", type: "address" }]
185
+ }
186
+ ];
187
+
188
+ // src/perp/buildPerpDepositWithGasDeduction.ts
189
+ function buildPerpDepositWithGasDeduction(params) {
190
+ if (params.amount <= 0n) {
191
+ throw new Error("buildPerpDepositWithGasDeduction: amount must be positive");
192
+ }
193
+ if (params.layerZeroFee < 0n) {
194
+ throw new Error(
195
+ "buildPerpDepositWithGasDeduction: layerZeroFee cannot be negative"
196
+ );
197
+ }
198
+ if (params.depositData.tokenAmount !== params.amount) {
199
+ throw new Error(
200
+ `buildPerpDepositWithGasDeduction: depositData.tokenAmount (${params.depositData.tokenAmount}) must equal amount (${params.amount})`
201
+ );
202
+ }
203
+ const vault = params.vaultAddress ?? ORDERLY_VAULT_ADDRESSES[params.chainId];
204
+ if (!vault) {
205
+ throw new Error(
206
+ `buildPerpDepositWithGasDeduction: no Orderly Vault address for chainId ${params.chainId}`
207
+ );
208
+ }
209
+ const depositCallData = encodeFunctionData({
210
+ abi: ORDERLY_VAULT_ABI,
211
+ functionName: "deposit",
212
+ args: [params.depositData]
213
+ });
214
+ const operations = [
215
+ erc20ApproveOp(params.usdcAddress, vault, params.amount),
216
+ {
217
+ ...rawCallOp(vault, depositCallData),
218
+ // BatchExecutor passes `value` from the inner call along; this
219
+ // becomes the LayerZero fee. The aggregated `msg.value` for the
220
+ // top-level UserOp must equal the sum of inner `value`s.
221
+ value: params.layerZeroFee
222
+ }
223
+ ];
224
+ return buildPartialUserOperation({
225
+ sender: params.userAddress,
226
+ nonce: params.aaNonce,
227
+ operations,
228
+ gasLimits: {
229
+ callGasLimit: params.gasLimits?.callGasLimit ?? 800000n,
230
+ verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150000n,
231
+ preVerificationGas: params.gasLimits?.preVerificationGas ?? 50000n
232
+ }
233
+ });
234
+ }
235
+
96
236
  // src/userop/types.ts
97
237
  var ENTRY_POINT_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
98
238
  var ZERO_VALUE = 0n;
@@ -187,25 +327,27 @@ var POINT_TOKEN_ABI = parseAbi([
187
327
 
188
328
  // src/contracts/real/batchExecutor.ts
189
329
  var BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA = "0x000000000000000000000000000000000000DE01";
190
- var BATCH_EXECUTOR_ADDRESS_BASE_MAINNET = "0x000000000000000000000000000000000000DE02";
330
+ var BATCH_EXECUTOR_ADDRESS_BASE_MAINNET = "0x7702cb554e6bFb442cb743A7dF23154544a7176C";
191
331
 
192
332
  // src/contracts/real/addresses.ts
193
333
  var PLACEHOLDER_DEAD = (suffix) => `0x000000000000000000000000000000000000${suffix.toLowerCase().padStart(4, "0")}`;
194
334
  var CONTRACT_ADDRESSES = {
195
- // Base mainnet — SC-delivered 2026-04-21
196
- // registry: IssuerRegistry 0xda2D3338CF70F462Ac175F5f2edfa45660CA4f31
197
- // factory: PointTokenFactory 0x36c0BAb2faBE45EfA6d13001143e43A266Af673B
198
- // oracle: MintingOracle 0xD85165939C700E51c8a45099316C6482634C2Ab9
199
- // tokenImpl: PointToken (impl) 0x2e6FB1B0C1A51abb83eC974890126a64eC02E995
200
- // mockUsdt: MockERC20 0x5d313485Ba59C3bb91e1A9C0C11782F0b83d5dcd
201
- // POINT: PointToken instance 0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e
335
+ // Base mainnet — SC-delivered (2026-04-21, 2026-04-22)
336
+ // registry: IssuerRegistry 0xda2D3338CF70F462Ac175F5f2edfa45660CA4f31
337
+ // factory: PointTokenFactory 0x36c0BAb2faBE45EfA6d13001143e43A266Af673B
338
+ // oracle: MintingOracle 0xD85165939C700E51c8a45099316C6482634C2Ab9
339
+ // tokenImpl: PointToken (impl) 0x2e6FB1B0C1A51abb83eC974890126a64eC02E995
340
+ // mockUsdt: MockERC20 0x5d313485Ba59C3bb91e1A9C0C11782F0b83d5dcd
341
+ // POINT: PointToken instance 0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e
342
+ // batchExecutor: Coinbase SW v2 0x7702cb554e6bFb442cb743A7dF23154544a7176C
343
+ // pafiHook: PAFIHook (V4, 10%) 0x870cAF9882d3160602AaC1769C2B264A2d8EC044
202
344
  8453: {
203
345
  pointToken: "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e",
204
- batchExecutor: PLACEHOLDER_DEAD("de02"),
205
- // ⏳ pending SC delivery
346
+ batchExecutor: "0x7702cb554e6bFb442cb743A7dF23154544a7176C",
206
347
  usdt: "0x5d313485Ba59C3bb91e1A9C0C11782F0b83d5dcd",
207
348
  issuerRegistry: "0xda2D3338CF70F462Ac175F5f2edfa45660CA4f31",
208
- mintingOracle: "0xD85165939C700E51c8a45099316C6482634C2Ab9"
349
+ mintingOracle: "0xD85165939C700E51c8a45099316C6482634C2Ab9",
350
+ pafiHook: "0x870cAF9882d3160602AaC1769C2B264A2d8EC044"
209
351
  },
210
352
  // Base Sepolia — not in active use; placeholders kept so the map
211
353
  // compiles for tooling that enumerates chains.
@@ -214,7 +356,8 @@ var CONTRACT_ADDRESSES = {
214
356
  batchExecutor: PLACEHOLDER_DEAD("de01"),
215
357
  usdt: PLACEHOLDER_DEAD("dead"),
216
358
  issuerRegistry: PLACEHOLDER_DEAD("dead"),
217
- mintingOracle: PLACEHOLDER_DEAD("dead")
359
+ mintingOracle: PLACEHOLDER_DEAD("dead"),
360
+ pafiHook: PLACEHOLDER_DEAD("dead")
218
361
  }
219
362
  };
220
363
  var POINT_TOKEN_FACTORY_ADDRESSES = {
@@ -547,11 +690,15 @@ export {
547
690
  BATCH_EXECUTOR_ABI,
548
691
  BATCH_EXECUTOR_ADDRESS_BASE_MAINNET,
549
692
  BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA,
693
+ BROKER_HASHES,
550
694
  COMMON_POOLS,
551
695
  COMMON_TOKENS,
552
696
  CONTRACT_ADDRESSES,
553
697
  ConfigurationError,
554
698
  ENTRY_POINT_V07,
699
+ ORDERLY_VAULT_ABI,
700
+ ORDERLY_VAULT_ADDRESSES,
701
+ ORDERLY_VAULT_BASE_MAINNET,
555
702
  POINT_TOKEN_FACTORY_ADDRESSES,
556
703
  POINT_TOKEN_IMPL_ADDRESSES,
557
704
  POINT_TOKEN_POOLS,
@@ -564,6 +711,7 @@ export {
564
711
  SigningError,
565
712
  SimulationError,
566
713
  TAKE_ALL,
714
+ TOKEN_HASHES,
567
715
  UNIVERSAL_ROUTER_ADDRESSES,
568
716
  V4_QUOTER_ADDRESSES,
569
717
  V4_SWAP,
@@ -577,6 +725,7 @@ export {
577
725
  buildMintRequestTypedData,
578
726
  buildPartialUserOperation,
579
727
  buildPermit2ApprovalCalldata,
728
+ buildPerpDepositWithGasDeduction,
580
729
  buildReceiverConsentTypedData,
581
730
  buildSwapFromQuote,
582
731
  buildSwapWithGasDeduction,
@@ -586,6 +735,7 @@ export {
586
735
  checkAllowance,
587
736
  checkEthAndBranch,
588
737
  combineRoutes,
738
+ computeAccountId,
589
739
  createLoginMessage,
590
740
  encodeBatchExecute,
591
741
  erc20Abi,