@pafi-dev/trading 0.1.0

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/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # @pafi-dev/trading
2
+
3
+ [![npm](https://img.shields.io/npm/v/@pafi-dev/trading)](https://www.npmjs.com/package/@pafi-dev/trading)
4
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+
6
+ Stateless on-chain trading handlers for PAFI — swap PT → USDT and deposit USDC into Orderly perp.
7
+
8
+ All handlers are purely on-chain (Uniswap V4 Quoter + UniversalRouter + Orderly Vault). No
9
+ ledger, no signer, no database. Safe to use in any backend (NestJS, Express, Hono) or
10
+ client-side (React, React Native).
11
+
12
+ ---
13
+
14
+ ## Requirements
15
+
16
+ - Node.js >= 18 (server) or any modern bundler (client)
17
+ - TypeScript >= 5.0
18
+ - `viem` ^2.0.0 and `@pafi-dev/core` ^0.5.0 (peer dependencies)
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @pafi-dev/trading @pafi-dev/core viem
26
+ # or
27
+ pnpm add @pafi-dev/trading @pafi-dev/core viem
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick start
33
+
34
+ ```ts
35
+ import { createPublicClient, http } from "viem";
36
+ import { base } from "viem/chains";
37
+ import { TradingHandlers } from "@pafi-dev/trading";
38
+
39
+ const provider = createPublicClient({ chain: base, transport: http(RPC_URL) });
40
+ const trading = new TradingHandlers({ provider, chainId: 8453 });
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Handlers
46
+
47
+ ### `handleQuote` — GET /quote
48
+
49
+ Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter. Uses multicall to batch all
50
+ candidate routes into a single RPC call.
51
+
52
+ ```ts
53
+ const quote = await trading.handleQuote({
54
+ chainId: 8453,
55
+ pointTokenAddress: "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e",
56
+ amount: 1000n * 10n ** 18n, // 1000 PT
57
+ pools: poolsFromSubgraph, // optional — combined with COMMON_POOLS
58
+ });
59
+
60
+ // quote.estimatedUsdtOut — raw USDT (6 decimals)
61
+ // quote.gasEstimate — estimated gas units
62
+ // quote.quoteError — "QUOTE_UNAVAILABLE" | "AMOUNT_TOO_SMALL_FOR_GAS" | undefined
63
+ ```
64
+
65
+ Returns `quoteError` instead of throwing when no pool/path is found, so the caller can
66
+ show a soft "unavailable" UI state without 500-ing.
67
+
68
+ ---
69
+
70
+ ### `handleSwap` — POST /swap
71
+
72
+ Quote the best route, apply slippage, and build an unsigned `PartialUserOperation` that
73
+ batches: `PT.approve` → `Permit2.approve` → `UniversalRouter.execute` (→ `PT.transfer` fee).
74
+
75
+ ```ts
76
+ const swap = await trading.handleSwap({
77
+ chainId: 8453,
78
+ userAddress: "0xUserEOA",
79
+ pointTokenAddress: "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e",
80
+ amount: 1000n * 10n ** 18n,
81
+ aaNonce: 0n, // from EntryPoint.getNonce(user, 0)
82
+ slippageBps: 50, // 0.5% — default
83
+ pools: poolsFromSubgraph,
84
+ // optional gas fee deduction in PT:
85
+ // gasFeePt: 5n * 10n ** 18n,
86
+ // feeRecipient: "0xOperatorAddress",
87
+ });
88
+
89
+ // swap.userOp — unsigned PartialUserOperation
90
+ // swap.estimatedUsdtOut — raw USDT before slippage
91
+ // swap.minAmountOut — encoded in UserOp (revert if less)
92
+ // swap.deadline — unix seconds (now + 5 min)
93
+ ```
94
+
95
+ After receiving the response, the frontend:
96
+ 1. Requests paymaster sponsorship from `POST /pimlico` (sponsor-relayer, Privy auth)
97
+ 2. Signs the UserOp hash via Privy
98
+ 3. Submits to the Bundler
99
+
100
+ ---
101
+
102
+ ### `handlePerpDeposit` — POST /perp-deposit
103
+
104
+ Quote the LayerZero fee and build an unsigned UserOp that batches:
105
+ `USDC.approve(vault)` → `vault.deposit{value: layerZeroFee}(data)`.
106
+
107
+ ```ts
108
+ const deposit = await trading.handlePerpDeposit({
109
+ chainId: 8453,
110
+ userAddress: "0xUserEOA",
111
+ amount: 100_000_000n, // 100 USDC (6 decimals)
112
+ aaNonce: 0n,
113
+ brokerId: "woofi_pro", // "woofi_pro" | "orderly" | "logx"
114
+ });
115
+
116
+ // deposit.userOp — unsigned PartialUserOperation
117
+ // deposit.layerZeroFee — ETH wei — user wallet must hold this as native ETH
118
+ // deposit.accountId — Orderly account ID for (user, broker) pair
119
+ // deposit.brokerHash — keccak256(brokerId)
120
+ // deposit.usdcAddress — USDC resolved from Vault.getAllowedToken()
121
+ ```
122
+
123
+ > **Native ETH constraint:** The paymaster sponsors ERC-4337 gas, but `msg.value` (the
124
+ > LayerZero fee) must come from the user's own native ETH balance. Surface `layerZeroFee`
125
+ > in the UI and warn the user if their ETH is insufficient before submitting.
126
+
127
+ ---
128
+
129
+ ## Wiring into a NestJS controller
130
+
131
+ ```ts
132
+ import { Controller, Get, Post, Body, Query } from "@nestjs/common";
133
+ import { TradingHandlers } from "@pafi-dev/trading";
134
+
135
+ @Controller()
136
+ export class TradingController {
137
+ constructor(private readonly trading: TradingHandlers) {}
138
+
139
+ @Get("quote")
140
+ async quote(@Query() q: QuoteQueryDto) {
141
+ return this.trading.handleQuote({
142
+ chainId: q.chainId,
143
+ pointTokenAddress: q.pointToken,
144
+ amount: BigInt(q.amount),
145
+ });
146
+ }
147
+
148
+ @Post("swap")
149
+ async swap(@Body() body: SwapBodyDto) {
150
+ return this.trading.handleSwap({
151
+ chainId: body.chainId,
152
+ userAddress: body.userAddress,
153
+ pointTokenAddress: body.pointTokenAddress,
154
+ amount: BigInt(body.amount),
155
+ aaNonce: BigInt(body.aaNonce),
156
+ slippageBps: body.slippageBps,
157
+ });
158
+ }
159
+
160
+ @Post("perp-deposit")
161
+ async perpDeposit(@Body() body: PerpDepositBodyDto) {
162
+ return this.trading.handlePerpDeposit({
163
+ chainId: body.chainId,
164
+ userAddress: body.userAddress,
165
+ amount: BigInt(body.amount),
166
+ aaNonce: BigInt(body.aaNonce),
167
+ brokerId: body.brokerId,
168
+ });
169
+ }
170
+ }
171
+ ```
172
+
173
+ Register `TradingHandlers` as a provider:
174
+
175
+ ```ts
176
+ import { createPublicClient, http } from "viem";
177
+ import { base } from "viem/chains";
178
+ import { TradingHandlers } from "@pafi-dev/trading";
179
+
180
+ @Module({
181
+ providers: [
182
+ {
183
+ provide: TradingHandlers,
184
+ useFactory: (config: ConfigService) =>
185
+ new TradingHandlers({
186
+ provider: createPublicClient({
187
+ chain: base,
188
+ transport: http(config.get("RPC_URL")),
189
+ }),
190
+ chainId: config.get<number>("CHAIN_ID"),
191
+ }),
192
+ inject: [ConfigService],
193
+ },
194
+ TradingController,
195
+ ],
196
+ })
197
+ export class TradingModule {}
198
+ ```
199
+
200
+ ---
201
+
202
+ ## API types
203
+
204
+ All request/response types are exported for use in frontend SDKs or OpenAPI generation:
205
+
206
+ ```ts
207
+ import type {
208
+ ApiQuoteRequest,
209
+ ApiQuoteResponse,
210
+ ApiQuoteError,
211
+ ApiSwapRequest,
212
+ ApiSwapResponse,
213
+ ApiPerpDepositRequest,
214
+ ApiPerpDepositResponse,
215
+ } from "@pafi-dev/trading";
216
+ ```
217
+
218
+ Since `bigint` does not serialize to JSON natively, HTTP controllers should convert
219
+ `bigint` fields to strings in the response (e.g. `.toString()`).
220
+
221
+ ---
222
+
223
+ ## Relationship to other PAFI packages
224
+
225
+ | Package | Scope |
226
+ |---|---|
227
+ | `@pafi-dev/core` | Chain primitives — EIP-712, UserOp builders, on-chain reads, ABIs |
228
+ | `@pafi-dev/issuer` | Issuer backend — mint/redeem auth, ledger, policy, indexer |
229
+ | `@pafi-dev/trading` | On-chain trading — swap quote + UserOp, perp deposit UserOp |
230
+
231
+ `@pafi-dev/trading` depends on `@pafi-dev/core` for `findBestQuote`,
232
+ `buildSwapWithGasDeduction`, `buildPerpDepositWithGasDeduction`, and contract addresses.
233
+ It does not depend on `@pafi-dev/issuer`.
234
+
235
+ ---
236
+
237
+ ## License
238
+
239
+ Apache-2.0
package/dist/index.cjs ADDED
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ TradingHandlers: () => TradingHandlers
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/api/handlers.ts
28
+ var import_viem = require("viem");
29
+ var import_core = require("@pafi-dev/core");
30
+ var TradingHandlers = class {
31
+ provider;
32
+ chainId;
33
+ constructor(config) {
34
+ this.provider = config.provider;
35
+ this.chainId = config.chainId;
36
+ }
37
+ // =========================================================================
38
+ // GET /quote
39
+ // =========================================================================
40
+ /**
41
+ * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.
42
+ *
43
+ * Uses multicall to batch all candidate routes into a single RPC call.
44
+ * Returns `quoteError: "QUOTE_UNAVAILABLE"` when no pool/path exists
45
+ * rather than throwing, so callers can show a soft "unavailable" UI
46
+ * state without 500-ing.
47
+ */
48
+ async handleQuote(request) {
49
+ if (request.chainId !== this.chainId) {
50
+ throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);
51
+ }
52
+ if (request.amount === 0n) {
53
+ return { pointAmount: 0n, estimatedUsdtOut: 0n, gasEstimate: 0n };
54
+ }
55
+ const { usdt } = (0, import_core.getContractAddresses)(request.chainId);
56
+ const pointTokenAddress = (0, import_viem.getAddress)(request.pointTokenAddress);
57
+ const pools = request.pools ?? [];
58
+ try {
59
+ const best = await (0, import_core.findBestQuote)(
60
+ this.provider,
61
+ request.chainId,
62
+ pointTokenAddress,
63
+ usdt,
64
+ request.amount,
65
+ pools
66
+ );
67
+ return {
68
+ pointAmount: request.amount,
69
+ estimatedUsdtOut: best.bestRoute.amountOut,
70
+ gasEstimate: best.bestRoute.gasEstimate
71
+ };
72
+ } catch {
73
+ return {
74
+ pointAmount: request.amount,
75
+ estimatedUsdtOut: 0n,
76
+ gasEstimate: 0n,
77
+ quoteError: "QUOTE_UNAVAILABLE"
78
+ };
79
+ }
80
+ }
81
+ // =========================================================================
82
+ // POST /swap
83
+ // =========================================================================
84
+ /**
85
+ * Build a PT → USDT swap UserOp.
86
+ *
87
+ * Quotes the best route, applies slippage, then encodes a 4-step
88
+ * batch: PT.approve → Permit2.approve → UniversalRouter.execute →
89
+ * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned
90
+ * `PartialUserOperation`; caller attaches paymaster data + user
91
+ * signature and submits to the Bundler.
92
+ */
93
+ async handleSwap(request) {
94
+ if (request.chainId !== this.chainId) {
95
+ throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);
96
+ }
97
+ if (request.amount <= 0n) {
98
+ throw new Error("handleSwap: amount must be positive");
99
+ }
100
+ const { usdt } = (0, import_core.getContractAddresses)(request.chainId);
101
+ const universalRouter = import_core.UNIVERSAL_ROUTER_ADDRESSES[request.chainId];
102
+ if (!universalRouter) {
103
+ throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);
104
+ }
105
+ const pointTokenAddress = (0, import_viem.getAddress)(request.pointTokenAddress);
106
+ const userAddress = (0, import_viem.getAddress)(request.userAddress);
107
+ const pools = request.pools ?? [];
108
+ const slippageBps = request.slippageBps ?? 50;
109
+ const gasFeePt = request.gasFeePt ?? 0n;
110
+ if (gasFeePt > 0n && !request.feeRecipient) {
111
+ throw new Error("handleSwap: feeRecipient required when gasFeePt > 0");
112
+ }
113
+ let quoteResult;
114
+ try {
115
+ quoteResult = await (0, import_core.findBestQuote)(
116
+ this.provider,
117
+ request.chainId,
118
+ pointTokenAddress,
119
+ usdt,
120
+ request.amount,
121
+ pools
122
+ );
123
+ } catch {
124
+ throw new Error("handleSwap: no swap path found for this point token");
125
+ }
126
+ const estimatedUsdtOut = quoteResult.bestRoute.amountOut;
127
+ const minAmountOut = estimatedUsdtOut * BigInt(1e4 - slippageBps) / 10000n;
128
+ const deadline = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
129
+ const userOp = (0, import_core.buildSwapWithGasDeduction)({
130
+ userAddress,
131
+ aaNonce: request.aaNonce,
132
+ pointTokenAddress,
133
+ outputTokenAddress: usdt,
134
+ universalRouterAddress: universalRouter,
135
+ amountIn: request.amount,
136
+ minAmountOut,
137
+ swapPath: quoteResult.bestRoute.path,
138
+ deadline,
139
+ gasFeePt,
140
+ feeRecipient: request.feeRecipient ?? userAddress
141
+ });
142
+ return { userOp, estimatedUsdtOut, minAmountOut, deadline };
143
+ }
144
+ // =========================================================================
145
+ // POST /perp-deposit
146
+ // =========================================================================
147
+ /**
148
+ * Build an Orderly perp deposit UserOp.
149
+ *
150
+ * Resolves USDC address and LayerZero fee from on-chain Vault reads,
151
+ * then encodes a 2-step batch: USDC.approve → Vault.deposit{value}.
152
+ * The `layerZeroFee` in the response is the ETH the user must hold
153
+ * natively — paymaster sponsors ERC-4337 gas only, not msg.value.
154
+ */
155
+ async handlePerpDeposit(request) {
156
+ if (request.chainId !== this.chainId) {
157
+ throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);
158
+ }
159
+ if (request.amount <= 0n) {
160
+ throw new Error("handlePerpDeposit: amount must be positive");
161
+ }
162
+ const vault = import_core.ORDERLY_VAULT_ADDRESSES[request.chainId];
163
+ if (!vault) {
164
+ throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);
165
+ }
166
+ const brokerHash = import_core.BROKER_HASHES[request.brokerId];
167
+ if (!brokerHash) {
168
+ throw new Error(`handlePerpDeposit: unknown brokerId "${request.brokerId}"`);
169
+ }
170
+ const tokenHash = import_core.TOKEN_HASHES.USDC;
171
+ const userAddress = (0, import_viem.getAddress)(request.userAddress);
172
+ const [usdcAddress, brokerAllowed] = await Promise.all([
173
+ this.provider.readContract({
174
+ address: vault,
175
+ abi: import_core.ORDERLY_VAULT_ABI,
176
+ functionName: "getAllowedToken",
177
+ args: [tokenHash]
178
+ }),
179
+ this.provider.readContract({
180
+ address: vault,
181
+ abi: import_core.ORDERLY_VAULT_ABI,
182
+ functionName: "getAllowedBroker",
183
+ args: [brokerHash]
184
+ })
185
+ ]);
186
+ if (!brokerAllowed) {
187
+ throw new Error(
188
+ `handlePerpDeposit: broker "${request.brokerId}" is not whitelisted on Orderly Vault`
189
+ );
190
+ }
191
+ const accountId = (0, import_core.computeAccountId)(userAddress, brokerHash);
192
+ const depositData = {
193
+ accountId,
194
+ brokerHash,
195
+ tokenHash,
196
+ tokenAmount: request.amount
197
+ };
198
+ const layerZeroFee = await this.provider.readContract({
199
+ address: vault,
200
+ abi: import_core.ORDERLY_VAULT_ABI,
201
+ functionName: "getDepositFee",
202
+ args: [userAddress, depositData]
203
+ });
204
+ const userOp = (0, import_core.buildPerpDepositWithGasDeduction)({
205
+ userAddress,
206
+ aaNonce: request.aaNonce,
207
+ chainId: request.chainId,
208
+ usdcAddress,
209
+ amount: request.amount,
210
+ depositData,
211
+ layerZeroFee
212
+ });
213
+ return { userOp, layerZeroFee, accountId, brokerHash, usdcAddress };
214
+ }
215
+ };
216
+ // Annotate the CommonJS export names for ESM import in node:
217
+ 0 && (module.exports = {
218
+ TradingHandlers
219
+ });
220
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/api/handlers.ts"],"sourcesContent":["export { TradingHandlers } from \"./api/handlers\";\nexport type { TradingHandlersConfig } from \"./api/handlers\";\n\nexport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiQuoteError,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./api/types\";\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n findBestQuote,\n buildSwapWithGasDeduction,\n buildPerpDepositWithGasDeduction,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n} from \"@pafi-dev/core\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return { pointAmount: 0n, estimatedUsdtOut: 0n, gasEstimate: 0n };\n }\n\n const { usdt } = getContractAddresses(request.chainId);\n const pointTokenAddress = getAddress(request.pointTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n pointTokenAddress,\n usdt,\n request.amount,\n pools,\n );\n return {\n pointAmount: request.amount,\n estimatedUsdtOut: best.bestRoute.amountOut,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n pointAmount: request.amount,\n estimatedUsdtOut: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a PT → USDT swap UserOp.\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: PT.approve → Permit2.approve → UniversalRouter.execute →\n * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned\n * `PartialUserOperation`; caller attaches paymaster data + user\n * signature and submits to the Bundler.\n */\n async handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { usdt } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const pointTokenAddress = getAddress(request.pointTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n const slippageBps = request.slippageBps ?? 50;\n const gasFeePt = request.gasFeePt ?? 0n;\n\n if (gasFeePt > 0n && !request.feeRecipient) {\n throw new Error(\"handleSwap: feeRecipient required when gasFeePt > 0\");\n }\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n pointTokenAddress,\n usdt,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\"handleSwap: no swap path found for this point token\");\n }\n\n const estimatedUsdtOut = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedUsdtOut * BigInt(10000 - slippageBps)) / 10000n;\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress,\n outputTokenAddress: usdt,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeePt,\n feeRecipient: request.feeRecipient ?? userAddress,\n });\n\n return { userOp, estimatedUsdtOut, minAmountOut, deadline };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Resolves USDC address and LayerZero fee from on-chain Vault reads,\n * then encodes a 2-step batch: USDC.approve → Vault.deposit{value}.\n * The `layerZeroFee` in the response is the ETH the user must hold\n * natively — paymaster sponsors ERC-4337 gas only, not msg.value.\n */\n async handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n const layerZeroFee = await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n }) as bigint;\n\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return { userOp, layerZeroFee, accountId, brokerHash, usdcAddress };\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,140 @@
1
+ import { Address, PublicClient } from 'viem';
2
+ import { PartialUserOperation, PoolKey } from '@pafi-dev/core';
3
+
4
+ interface ApiQuoteRequest {
5
+ chainId: number;
6
+ pointTokenAddress: Address;
7
+ /** PT amount (18-decimal raw units). */
8
+ amount: bigint;
9
+ /**
10
+ * PT/USDT pools to include in routing. Combined with COMMON_POOLS
11
+ * from `@pafi-dev/core`. Pass empty array to use only COMMON_POOLS.
12
+ */
13
+ pools?: PoolKey[];
14
+ }
15
+ type ApiQuoteError = "QUOTE_UNAVAILABLE" | "AMOUNT_TOO_SMALL_FOR_GAS";
16
+ interface ApiQuoteResponse {
17
+ pointAmount: bigint;
18
+ estimatedUsdtOut: bigint;
19
+ gasEstimate: bigint;
20
+ quoteError?: ApiQuoteError;
21
+ }
22
+ interface ApiSwapRequest {
23
+ chainId: number;
24
+ userAddress: Address;
25
+ pointTokenAddress: Address;
26
+ /** PT amount to swap (18-decimal raw units). */
27
+ amount: bigint;
28
+ /** ERC-4337 account nonce for the user's EOA (from EntryPoint). */
29
+ aaNonce: bigint;
30
+ /** Slippage tolerance in basis points (1 bps = 0.01%). Default: 50 (0.5%). */
31
+ slippageBps?: number;
32
+ /** PT/USDT pools. Combined with COMMON_POOLS. Pass empty to use only COMMON_POOLS. */
33
+ pools?: PoolKey[];
34
+ /**
35
+ * PT amount deducted from user balance as operator gas fee.
36
+ * Default: 0n (no fee deduction). When > 0, `feeRecipient` is required.
37
+ */
38
+ gasFeePt?: bigint;
39
+ /** Recipient of the gasFeePt deduction. Required when gasFeePt > 0. */
40
+ feeRecipient?: Address;
41
+ }
42
+ interface ApiSwapResponse {
43
+ /** Unsigned UserOp — attach paymaster data + user signature, then submit to Bundler. */
44
+ userOp: PartialUserOperation;
45
+ /** Raw USDT out before slippage (6 decimals). For display. */
46
+ estimatedUsdtOut: bigint;
47
+ /** Minimum USDT accepted — encoded in the UserOp calldata. */
48
+ minAmountOut: bigint;
49
+ /** Swap deadline (unix seconds). Re-request if user doesn't submit in time. */
50
+ deadline: bigint;
51
+ }
52
+ interface ApiPerpDepositRequest {
53
+ chainId: number;
54
+ userAddress: Address;
55
+ /** USDC amount to deposit (6-decimal raw units). */
56
+ amount: bigint;
57
+ /** ERC-4337 account nonce for the user's EOA (from EntryPoint). */
58
+ aaNonce: bigint;
59
+ /**
60
+ * Orderly broker ID — must be whitelisted on the Vault.
61
+ * Known values: woofi_pro, orderly, logx.
62
+ */
63
+ brokerId: string;
64
+ }
65
+ interface ApiPerpDepositResponse {
66
+ /** Unsigned UserOp — attach paymaster data + user signature, then submit to Bundler. */
67
+ userOp: PartialUserOperation;
68
+ /**
69
+ * LayerZero fee in ETH wei. User wallet must hold at least this as
70
+ * native ETH — even when ERC-4337 gas is sponsored by paymaster.
71
+ */
72
+ layerZeroFee: bigint;
73
+ /** Orderly account ID for this (user, broker) pair. */
74
+ accountId: `0x${string}`;
75
+ /** keccak256(brokerId) — for client-side verification. */
76
+ brokerHash: `0x${string}`;
77
+ /** USDC contract address (resolved from Vault.getAllowedToken). */
78
+ usdcAddress: Address;
79
+ }
80
+
81
+ interface TradingHandlersConfig {
82
+ provider: PublicClient;
83
+ chainId: number;
84
+ }
85
+ /**
86
+ * Framework-agnostic handlers for on-chain trading actions.
87
+ *
88
+ * All handlers are stateless — they need only a PublicClient for RPC
89
+ * calls. No ledger, no signer, no DB. Issuers wrap these in their own
90
+ * HTTP controllers (Express / NestJS / Hono / etc.) the same way they
91
+ * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.
92
+ *
93
+ * Example (NestJS):
94
+ *
95
+ * const trading = new TradingHandlers({ provider, chainId });
96
+ *
97
+ * // GET /quote
98
+ * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });
99
+ *
100
+ * // POST /swap
101
+ * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });
102
+ *
103
+ * // POST /perp-deposit
104
+ * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });
105
+ */
106
+ declare class TradingHandlers {
107
+ private readonly provider;
108
+ private readonly chainId;
109
+ constructor(config: TradingHandlersConfig);
110
+ /**
111
+ * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.
112
+ *
113
+ * Uses multicall to batch all candidate routes into a single RPC call.
114
+ * Returns `quoteError: "QUOTE_UNAVAILABLE"` when no pool/path exists
115
+ * rather than throwing, so callers can show a soft "unavailable" UI
116
+ * state without 500-ing.
117
+ */
118
+ handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse>;
119
+ /**
120
+ * Build a PT → USDT swap UserOp.
121
+ *
122
+ * Quotes the best route, applies slippage, then encodes a 4-step
123
+ * batch: PT.approve → Permit2.approve → UniversalRouter.execute →
124
+ * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned
125
+ * `PartialUserOperation`; caller attaches paymaster data + user
126
+ * signature and submits to the Bundler.
127
+ */
128
+ handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse>;
129
+ /**
130
+ * Build an Orderly perp deposit UserOp.
131
+ *
132
+ * Resolves USDC address and LayerZero fee from on-chain Vault reads,
133
+ * then encodes a 2-step batch: USDC.approve → Vault.deposit{value}.
134
+ * The `layerZeroFee` in the response is the ETH the user must hold
135
+ * natively — paymaster sponsors ERC-4337 gas only, not msg.value.
136
+ */
137
+ handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
138
+ }
139
+
140
+ export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, TradingHandlers, type TradingHandlersConfig };
@@ -0,0 +1,140 @@
1
+ import { Address, PublicClient } from 'viem';
2
+ import { PartialUserOperation, PoolKey } from '@pafi-dev/core';
3
+
4
+ interface ApiQuoteRequest {
5
+ chainId: number;
6
+ pointTokenAddress: Address;
7
+ /** PT amount (18-decimal raw units). */
8
+ amount: bigint;
9
+ /**
10
+ * PT/USDT pools to include in routing. Combined with COMMON_POOLS
11
+ * from `@pafi-dev/core`. Pass empty array to use only COMMON_POOLS.
12
+ */
13
+ pools?: PoolKey[];
14
+ }
15
+ type ApiQuoteError = "QUOTE_UNAVAILABLE" | "AMOUNT_TOO_SMALL_FOR_GAS";
16
+ interface ApiQuoteResponse {
17
+ pointAmount: bigint;
18
+ estimatedUsdtOut: bigint;
19
+ gasEstimate: bigint;
20
+ quoteError?: ApiQuoteError;
21
+ }
22
+ interface ApiSwapRequest {
23
+ chainId: number;
24
+ userAddress: Address;
25
+ pointTokenAddress: Address;
26
+ /** PT amount to swap (18-decimal raw units). */
27
+ amount: bigint;
28
+ /** ERC-4337 account nonce for the user's EOA (from EntryPoint). */
29
+ aaNonce: bigint;
30
+ /** Slippage tolerance in basis points (1 bps = 0.01%). Default: 50 (0.5%). */
31
+ slippageBps?: number;
32
+ /** PT/USDT pools. Combined with COMMON_POOLS. Pass empty to use only COMMON_POOLS. */
33
+ pools?: PoolKey[];
34
+ /**
35
+ * PT amount deducted from user balance as operator gas fee.
36
+ * Default: 0n (no fee deduction). When > 0, `feeRecipient` is required.
37
+ */
38
+ gasFeePt?: bigint;
39
+ /** Recipient of the gasFeePt deduction. Required when gasFeePt > 0. */
40
+ feeRecipient?: Address;
41
+ }
42
+ interface ApiSwapResponse {
43
+ /** Unsigned UserOp — attach paymaster data + user signature, then submit to Bundler. */
44
+ userOp: PartialUserOperation;
45
+ /** Raw USDT out before slippage (6 decimals). For display. */
46
+ estimatedUsdtOut: bigint;
47
+ /** Minimum USDT accepted — encoded in the UserOp calldata. */
48
+ minAmountOut: bigint;
49
+ /** Swap deadline (unix seconds). Re-request if user doesn't submit in time. */
50
+ deadline: bigint;
51
+ }
52
+ interface ApiPerpDepositRequest {
53
+ chainId: number;
54
+ userAddress: Address;
55
+ /** USDC amount to deposit (6-decimal raw units). */
56
+ amount: bigint;
57
+ /** ERC-4337 account nonce for the user's EOA (from EntryPoint). */
58
+ aaNonce: bigint;
59
+ /**
60
+ * Orderly broker ID — must be whitelisted on the Vault.
61
+ * Known values: woofi_pro, orderly, logx.
62
+ */
63
+ brokerId: string;
64
+ }
65
+ interface ApiPerpDepositResponse {
66
+ /** Unsigned UserOp — attach paymaster data + user signature, then submit to Bundler. */
67
+ userOp: PartialUserOperation;
68
+ /**
69
+ * LayerZero fee in ETH wei. User wallet must hold at least this as
70
+ * native ETH — even when ERC-4337 gas is sponsored by paymaster.
71
+ */
72
+ layerZeroFee: bigint;
73
+ /** Orderly account ID for this (user, broker) pair. */
74
+ accountId: `0x${string}`;
75
+ /** keccak256(brokerId) — for client-side verification. */
76
+ brokerHash: `0x${string}`;
77
+ /** USDC contract address (resolved from Vault.getAllowedToken). */
78
+ usdcAddress: Address;
79
+ }
80
+
81
+ interface TradingHandlersConfig {
82
+ provider: PublicClient;
83
+ chainId: number;
84
+ }
85
+ /**
86
+ * Framework-agnostic handlers for on-chain trading actions.
87
+ *
88
+ * All handlers are stateless — they need only a PublicClient for RPC
89
+ * calls. No ledger, no signer, no DB. Issuers wrap these in their own
90
+ * HTTP controllers (Express / NestJS / Hono / etc.) the same way they
91
+ * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.
92
+ *
93
+ * Example (NestJS):
94
+ *
95
+ * const trading = new TradingHandlers({ provider, chainId });
96
+ *
97
+ * // GET /quote
98
+ * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });
99
+ *
100
+ * // POST /swap
101
+ * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });
102
+ *
103
+ * // POST /perp-deposit
104
+ * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });
105
+ */
106
+ declare class TradingHandlers {
107
+ private readonly provider;
108
+ private readonly chainId;
109
+ constructor(config: TradingHandlersConfig);
110
+ /**
111
+ * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.
112
+ *
113
+ * Uses multicall to batch all candidate routes into a single RPC call.
114
+ * Returns `quoteError: "QUOTE_UNAVAILABLE"` when no pool/path exists
115
+ * rather than throwing, so callers can show a soft "unavailable" UI
116
+ * state without 500-ing.
117
+ */
118
+ handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse>;
119
+ /**
120
+ * Build a PT → USDT swap UserOp.
121
+ *
122
+ * Quotes the best route, applies slippage, then encodes a 4-step
123
+ * batch: PT.approve → Permit2.approve → UniversalRouter.execute →
124
+ * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned
125
+ * `PartialUserOperation`; caller attaches paymaster data + user
126
+ * signature and submits to the Bundler.
127
+ */
128
+ handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse>;
129
+ /**
130
+ * Build an Orderly perp deposit UserOp.
131
+ *
132
+ * Resolves USDC address and LayerZero fee from on-chain Vault reads,
133
+ * then encodes a 2-step batch: USDC.approve → Vault.deposit{value}.
134
+ * The `layerZeroFee` in the response is the ETH the user must hold
135
+ * natively — paymaster sponsors ERC-4337 gas only, not msg.value.
136
+ */
137
+ handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
138
+ }
139
+
140
+ export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, TradingHandlers, type TradingHandlersConfig };
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ // src/api/handlers.ts
2
+ import { getAddress } from "viem";
3
+ import {
4
+ findBestQuote,
5
+ buildSwapWithGasDeduction,
6
+ buildPerpDepositWithGasDeduction,
7
+ getContractAddresses,
8
+ UNIVERSAL_ROUTER_ADDRESSES,
9
+ ORDERLY_VAULT_ABI,
10
+ ORDERLY_VAULT_ADDRESSES,
11
+ BROKER_HASHES,
12
+ TOKEN_HASHES,
13
+ computeAccountId
14
+ } from "@pafi-dev/core";
15
+ var TradingHandlers = class {
16
+ provider;
17
+ chainId;
18
+ constructor(config) {
19
+ this.provider = config.provider;
20
+ this.chainId = config.chainId;
21
+ }
22
+ // =========================================================================
23
+ // GET /quote
24
+ // =========================================================================
25
+ /**
26
+ * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.
27
+ *
28
+ * Uses multicall to batch all candidate routes into a single RPC call.
29
+ * Returns `quoteError: "QUOTE_UNAVAILABLE"` when no pool/path exists
30
+ * rather than throwing, so callers can show a soft "unavailable" UI
31
+ * state without 500-ing.
32
+ */
33
+ async handleQuote(request) {
34
+ if (request.chainId !== this.chainId) {
35
+ throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);
36
+ }
37
+ if (request.amount === 0n) {
38
+ return { pointAmount: 0n, estimatedUsdtOut: 0n, gasEstimate: 0n };
39
+ }
40
+ const { usdt } = getContractAddresses(request.chainId);
41
+ const pointTokenAddress = getAddress(request.pointTokenAddress);
42
+ const pools = request.pools ?? [];
43
+ try {
44
+ const best = await findBestQuote(
45
+ this.provider,
46
+ request.chainId,
47
+ pointTokenAddress,
48
+ usdt,
49
+ request.amount,
50
+ pools
51
+ );
52
+ return {
53
+ pointAmount: request.amount,
54
+ estimatedUsdtOut: best.bestRoute.amountOut,
55
+ gasEstimate: best.bestRoute.gasEstimate
56
+ };
57
+ } catch {
58
+ return {
59
+ pointAmount: request.amount,
60
+ estimatedUsdtOut: 0n,
61
+ gasEstimate: 0n,
62
+ quoteError: "QUOTE_UNAVAILABLE"
63
+ };
64
+ }
65
+ }
66
+ // =========================================================================
67
+ // POST /swap
68
+ // =========================================================================
69
+ /**
70
+ * Build a PT → USDT swap UserOp.
71
+ *
72
+ * Quotes the best route, applies slippage, then encodes a 4-step
73
+ * batch: PT.approve → Permit2.approve → UniversalRouter.execute →
74
+ * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned
75
+ * `PartialUserOperation`; caller attaches paymaster data + user
76
+ * signature and submits to the Bundler.
77
+ */
78
+ async handleSwap(request) {
79
+ if (request.chainId !== this.chainId) {
80
+ throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);
81
+ }
82
+ if (request.amount <= 0n) {
83
+ throw new Error("handleSwap: amount must be positive");
84
+ }
85
+ const { usdt } = getContractAddresses(request.chainId);
86
+ const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];
87
+ if (!universalRouter) {
88
+ throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);
89
+ }
90
+ const pointTokenAddress = getAddress(request.pointTokenAddress);
91
+ const userAddress = getAddress(request.userAddress);
92
+ const pools = request.pools ?? [];
93
+ const slippageBps = request.slippageBps ?? 50;
94
+ const gasFeePt = request.gasFeePt ?? 0n;
95
+ if (gasFeePt > 0n && !request.feeRecipient) {
96
+ throw new Error("handleSwap: feeRecipient required when gasFeePt > 0");
97
+ }
98
+ let quoteResult;
99
+ try {
100
+ quoteResult = await findBestQuote(
101
+ this.provider,
102
+ request.chainId,
103
+ pointTokenAddress,
104
+ usdt,
105
+ request.amount,
106
+ pools
107
+ );
108
+ } catch {
109
+ throw new Error("handleSwap: no swap path found for this point token");
110
+ }
111
+ const estimatedUsdtOut = quoteResult.bestRoute.amountOut;
112
+ const minAmountOut = estimatedUsdtOut * BigInt(1e4 - slippageBps) / 10000n;
113
+ const deadline = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
114
+ const userOp = buildSwapWithGasDeduction({
115
+ userAddress,
116
+ aaNonce: request.aaNonce,
117
+ pointTokenAddress,
118
+ outputTokenAddress: usdt,
119
+ universalRouterAddress: universalRouter,
120
+ amountIn: request.amount,
121
+ minAmountOut,
122
+ swapPath: quoteResult.bestRoute.path,
123
+ deadline,
124
+ gasFeePt,
125
+ feeRecipient: request.feeRecipient ?? userAddress
126
+ });
127
+ return { userOp, estimatedUsdtOut, minAmountOut, deadline };
128
+ }
129
+ // =========================================================================
130
+ // POST /perp-deposit
131
+ // =========================================================================
132
+ /**
133
+ * Build an Orderly perp deposit UserOp.
134
+ *
135
+ * Resolves USDC address and LayerZero fee from on-chain Vault reads,
136
+ * then encodes a 2-step batch: USDC.approve → Vault.deposit{value}.
137
+ * The `layerZeroFee` in the response is the ETH the user must hold
138
+ * natively — paymaster sponsors ERC-4337 gas only, not msg.value.
139
+ */
140
+ async handlePerpDeposit(request) {
141
+ if (request.chainId !== this.chainId) {
142
+ throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);
143
+ }
144
+ if (request.amount <= 0n) {
145
+ throw new Error("handlePerpDeposit: amount must be positive");
146
+ }
147
+ const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];
148
+ if (!vault) {
149
+ throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);
150
+ }
151
+ const brokerHash = BROKER_HASHES[request.brokerId];
152
+ if (!brokerHash) {
153
+ throw new Error(`handlePerpDeposit: unknown brokerId "${request.brokerId}"`);
154
+ }
155
+ const tokenHash = TOKEN_HASHES.USDC;
156
+ const userAddress = getAddress(request.userAddress);
157
+ const [usdcAddress, brokerAllowed] = await Promise.all([
158
+ this.provider.readContract({
159
+ address: vault,
160
+ abi: ORDERLY_VAULT_ABI,
161
+ functionName: "getAllowedToken",
162
+ args: [tokenHash]
163
+ }),
164
+ this.provider.readContract({
165
+ address: vault,
166
+ abi: ORDERLY_VAULT_ABI,
167
+ functionName: "getAllowedBroker",
168
+ args: [brokerHash]
169
+ })
170
+ ]);
171
+ if (!brokerAllowed) {
172
+ throw new Error(
173
+ `handlePerpDeposit: broker "${request.brokerId}" is not whitelisted on Orderly Vault`
174
+ );
175
+ }
176
+ const accountId = computeAccountId(userAddress, brokerHash);
177
+ const depositData = {
178
+ accountId,
179
+ brokerHash,
180
+ tokenHash,
181
+ tokenAmount: request.amount
182
+ };
183
+ const layerZeroFee = await this.provider.readContract({
184
+ address: vault,
185
+ abi: ORDERLY_VAULT_ABI,
186
+ functionName: "getDepositFee",
187
+ args: [userAddress, depositData]
188
+ });
189
+ const userOp = buildPerpDepositWithGasDeduction({
190
+ userAddress,
191
+ aaNonce: request.aaNonce,
192
+ chainId: request.chainId,
193
+ usdcAddress,
194
+ amount: request.amount,
195
+ depositData,
196
+ layerZeroFee
197
+ });
198
+ return { userOp, layerZeroFee, accountId, brokerHash, usdcAddress };
199
+ }
200
+ };
201
+ export {
202
+ TradingHandlers
203
+ };
204
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/handlers.ts"],"sourcesContent":["import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n findBestQuote,\n buildSwapWithGasDeduction,\n buildPerpDepositWithGasDeduction,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n} from \"@pafi-dev/core\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return { pointAmount: 0n, estimatedUsdtOut: 0n, gasEstimate: 0n };\n }\n\n const { usdt } = getContractAddresses(request.chainId);\n const pointTokenAddress = getAddress(request.pointTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n pointTokenAddress,\n usdt,\n request.amount,\n pools,\n );\n return {\n pointAmount: request.amount,\n estimatedUsdtOut: best.bestRoute.amountOut,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n pointAmount: request.amount,\n estimatedUsdtOut: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a PT → USDT swap UserOp.\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: PT.approve → Permit2.approve → UniversalRouter.execute →\n * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned\n * `PartialUserOperation`; caller attaches paymaster data + user\n * signature and submits to the Bundler.\n */\n async handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { usdt } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const pointTokenAddress = getAddress(request.pointTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n const slippageBps = request.slippageBps ?? 50;\n const gasFeePt = request.gasFeePt ?? 0n;\n\n if (gasFeePt > 0n && !request.feeRecipient) {\n throw new Error(\"handleSwap: feeRecipient required when gasFeePt > 0\");\n }\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n pointTokenAddress,\n usdt,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\"handleSwap: no swap path found for this point token\");\n }\n\n const estimatedUsdtOut = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedUsdtOut * BigInt(10000 - slippageBps)) / 10000n;\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress,\n outputTokenAddress: usdt,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeePt,\n feeRecipient: request.feeRecipient ?? userAddress,\n });\n\n return { userOp, estimatedUsdtOut, minAmountOut, deadline };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Resolves USDC address and LayerZero fee from on-chain Vault reads,\n * then encodes a 2-step batch: USDC.approve → Vault.deposit{value}.\n * The `layerZeroFee` in the response is the ETH the user must hold\n * natively — paymaster sponsors ERC-4337 gas only, not msg.value.\n */\n async handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n const layerZeroFee = await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n }) as bigint;\n\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return { userOp, layerZeroFee, accountId, brokerHash, usdcAddress };\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@pafi-dev/trading",
3
+ "version": "0.1.0",
4
+ "description": "Stateless on-chain trading handlers for PAFI — swap, quote, perp deposit",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "dependencies": {
25
+ "@pafi-dev/core": "0.5.1"
26
+ },
27
+ "peerDependencies": {
28
+ "viem": "^2.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "tsup": "^8.0.0",
32
+ "typescript": "^5.5.0",
33
+ "viem": "^2.21.0",
34
+ "vitest": "^2.0.0"
35
+ },
36
+ "license": "Apache-2.0",
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "test": "vitest run --passWithNoTests",
40
+ "test:watch": "vitest",
41
+ "typecheck": "tsc --noEmit"
42
+ }
43
+ }