@pafi-dev/trading 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -1
- package/dist/index.cjs +3 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -15
- package/dist/index.d.ts +2 -15
- package/dist/index.js +1 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,12 @@ client-side (React, React Native).
|
|
|
15
15
|
|
|
16
16
|
- Node.js >= 18 (server) or any modern bundler (client)
|
|
17
17
|
- TypeScript >= 5.0
|
|
18
|
-
- `viem` ^2.0.0 and `@pafi-dev/core` ^0.5.
|
|
18
|
+
- `viem` ^2.0.0 and `@pafi-dev/core` ^0.5.17 (peer dependencies)
|
|
19
|
+
|
|
20
|
+
> **Latest:** `0.1.3` — bumps the `@pafi-dev/core` peer to `0.5.17` for
|
|
21
|
+
> the v0.8 EIP-712 userOpHash + `buildUserOpTypedData()` helpers required
|
|
22
|
+
> by the EIP-7702 swap path. Also adds a frontend "quote review"
|
|
23
|
+
> example. See [Changelog](#changelog).
|
|
19
24
|
|
|
20
25
|
---
|
|
21
26
|
|
|
@@ -65,6 +70,40 @@ const quote = await trading.handleQuote({
|
|
|
65
70
|
Returns `quoteError` instead of throwing when no pool/path is found, so the caller can
|
|
66
71
|
show a soft "unavailable" UI state without 500-ing.
|
|
67
72
|
|
|
73
|
+
#### Frontend "quote review" pattern
|
|
74
|
+
|
|
75
|
+
The mobile / web client should refresh `/quote` whenever the user types a
|
|
76
|
+
new amount, so the swap CTA always shows the exact USDT they will
|
|
77
|
+
receive (after the operator gas fee in USDT). Example: poll
|
|
78
|
+
`GET /quote?chainId&pointToken&amount` on a 400 ms debounce after each
|
|
79
|
+
keystroke and render a card with `pointAmount → estimatedUsdtOut −
|
|
80
|
+
gasFeeUsdt = netUsdtOut` plus the `exchangeRate`.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const [quote, setQuote] = useState<QuoteResponseDto | null>(null);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!token || amount <= 0n) return setQuote(null);
|
|
87
|
+
const t = setTimeout(async () => {
|
|
88
|
+
const url = `${API}/quote?chainId=8453&pointToken=${POINT_TOKEN}&amount=${amount}`;
|
|
89
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
90
|
+
setQuote(await res.json());
|
|
91
|
+
}, 400);
|
|
92
|
+
return () => clearTimeout(t);
|
|
93
|
+
}, [amount, token]);
|
|
94
|
+
|
|
95
|
+
// quote.estimatedUsdtOut → "you will receive ~Y USDT"
|
|
96
|
+
// quote.gasFeeUsdt → "operator gas fee deducted from output"
|
|
97
|
+
// quote.netUsdtOut → "actually credited to your wallet"
|
|
98
|
+
// quote.exchangeRate → "1 PT ≈ ${rate} USDT"
|
|
99
|
+
// quote.quoteError → render warning, hide the Swap CTA
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A working implementation lives in
|
|
103
|
+
[`privy-pimlico-eip7702-example/app/components/WalletPanel.tsx`](https://github.com/pacific-finance/pafi-backend/blob/main/privy-pimlico-eip7702-example/app/components/WalletPanel.tsx)
|
|
104
|
+
(search for `Quote review`). It reuses the same `/quote` endpoint the
|
|
105
|
+
issuer backend exposes — no extra SDK call needed on the client.
|
|
106
|
+
|
|
68
107
|
---
|
|
69
108
|
|
|
70
109
|
### `handleSwap` — POST /swap
|
|
@@ -234,6 +273,29 @@ It does not depend on `@pafi-dev/issuer`.
|
|
|
234
273
|
|
|
235
274
|
---
|
|
236
275
|
|
|
276
|
+
## Changelog
|
|
277
|
+
|
|
278
|
+
### 0.1.3
|
|
279
|
+
|
|
280
|
+
- Peer dependency `@pafi-dev/core` bumped to `0.5.17` so the swap path
|
|
281
|
+
picks up `buildUserOpTypedData()` + the v0.8 EIP-712 `computeUserOpHash`.
|
|
282
|
+
Required for the EIP-7702 mobile swap flow on Pimlico's
|
|
283
|
+
`Simple7702Account` — the previous v0.7 hash format and EIP-191
|
|
284
|
+
signing path both reverted with `AA24 signature error`. See
|
|
285
|
+
[`@pafi-dev/core` changelog 0.5.16](../core/README.md#0516).
|
|
286
|
+
- README adds a **frontend "quote review" pattern** — debounced
|
|
287
|
+
`/quote` polling so the user sees `X PT → ~Y USDT` (after operator
|
|
288
|
+
gas fee) before submitting the swap UserOp. Reference implementation
|
|
289
|
+
in `privy-pimlico-eip7702-example/app/components/WalletPanel.tsx`.
|
|
290
|
+
|
|
291
|
+
### 0.1.2
|
|
292
|
+
|
|
293
|
+
- Initial public release. `handleQuote`, `handleSwap`,
|
|
294
|
+
`handlePerpDeposit` stateless handlers + `fetchPafiPools` subgraph
|
|
295
|
+
helper.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
237
299
|
## License
|
|
238
300
|
|
|
239
301
|
Apache-2.0
|
package/dist/index.cjs
CHANGED
|
@@ -20,9 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
PAFI_SUBGRAPH_URL: () => PAFI_SUBGRAPH_URL,
|
|
23
|
+
PAFI_SUBGRAPH_URL: () => import_core2.PAFI_SUBGRAPH_URL,
|
|
24
24
|
TradingHandlers: () => TradingHandlers,
|
|
25
|
-
fetchPafiPools: () => fetchPafiPools
|
|
25
|
+
fetchPafiPools: () => import_core2.fetchPafiPools
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
|
|
@@ -217,68 +217,7 @@ var TradingHandlers = class {
|
|
|
217
217
|
};
|
|
218
218
|
|
|
219
219
|
// src/pools.ts
|
|
220
|
-
var
|
|
221
|
-
var PAFI_SUBGRAPH_URL = "https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v2";
|
|
222
|
-
var POOL_QUERY = `
|
|
223
|
-
query GetPoolForPointToken($id: ID!) {
|
|
224
|
-
pafiToken(id: $id) {
|
|
225
|
-
id
|
|
226
|
-
pool {
|
|
227
|
-
id
|
|
228
|
-
feeTier
|
|
229
|
-
tickSpacing
|
|
230
|
-
hooks
|
|
231
|
-
token0 { id }
|
|
232
|
-
token1 { id }
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
`;
|
|
237
|
-
function sortCurrencies(a, b) {
|
|
238
|
-
return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
|
|
239
|
-
}
|
|
240
|
-
async function fetchPafiPools(_chainId, pointTokenAddress, subgraphUrl = PAFI_SUBGRAPH_URL) {
|
|
241
|
-
let response;
|
|
242
|
-
try {
|
|
243
|
-
response = await fetch(subgraphUrl, {
|
|
244
|
-
method: "POST",
|
|
245
|
-
headers: { "Content-Type": "application/json" },
|
|
246
|
-
body: JSON.stringify({
|
|
247
|
-
query: POOL_QUERY,
|
|
248
|
-
variables: { id: pointTokenAddress.toLowerCase() }
|
|
249
|
-
})
|
|
250
|
-
});
|
|
251
|
-
} catch (err) {
|
|
252
|
-
console.warn("[fetchPafiPools] subgraph unreachable:", err.message);
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
if (!response.ok) {
|
|
256
|
-
console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);
|
|
257
|
-
return [];
|
|
258
|
-
}
|
|
259
|
-
const json = await response.json();
|
|
260
|
-
if (json.errors && json.errors.length > 0) {
|
|
261
|
-
console.warn("[fetchPafiPools] subgraph errors:", json.errors.map((e) => e.message).join("; "));
|
|
262
|
-
return [];
|
|
263
|
-
}
|
|
264
|
-
const pool = json.data?.pafiToken?.pool;
|
|
265
|
-
if (!pool) return [];
|
|
266
|
-
if (!(0, import_viem2.isAddress)(pool.hooks) || !(0, import_viem2.isAddress)(pool.token0.id) || !(0, import_viem2.isAddress)(pool.token1.id) || !Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
|
|
267
|
-
console.error("[fetchPafiPools] invalid pool data in subgraph response \u2014 skipping");
|
|
268
|
-
return [];
|
|
269
|
-
}
|
|
270
|
-
const [currency0, currency1] = sortCurrencies(
|
|
271
|
-
pool.token0.id,
|
|
272
|
-
pool.token1.id
|
|
273
|
-
);
|
|
274
|
-
return [{
|
|
275
|
-
currency0,
|
|
276
|
-
currency1,
|
|
277
|
-
fee: Number(pool.feeTier),
|
|
278
|
-
tickSpacing: Number(pool.tickSpacing),
|
|
279
|
-
hooks: pool.hooks
|
|
280
|
-
}];
|
|
281
|
-
}
|
|
220
|
+
var import_core2 = require("@pafi-dev/core");
|
|
282
221
|
// Annotate the CommonJS export names for ESM import in node:
|
|
283
222
|
0 && (module.exports = {
|
|
284
223
|
PAFI_SUBGRAPH_URL,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api/handlers.ts","../src/pools.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\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"./pools\";\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","import { isAddress } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { PoolKey } from \"@pafi-dev/core\";\n\nexport const PAFI_SUBGRAPH_URL =\n \"https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v2\";\n\nconst POOL_QUERY = `\n query GetPoolForPointToken($id: ID!) {\n pafiToken(id: $id) {\n id\n pool {\n id\n feeTier\n tickSpacing\n hooks\n token0 { id }\n token1 { id }\n }\n }\n }\n`;\n\ninterface GraphQLResponse {\n data?: {\n pafiToken: {\n id: string;\n pool: {\n id: string;\n feeTier: string;\n tickSpacing: string;\n hooks: string;\n token0: { id: string };\n token1: { id: string };\n } | null;\n } | null;\n };\n errors?: { message: string }[];\n}\n\nfunction sortCurrencies(a: Address, b: Address): [Address, Address] {\n return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];\n}\n\n/**\n * Fetch the Uniswap V4 pool(s) for a PAFI PointToken from the subgraph.\n *\n * Browser and Node compatible — uses globalThis.fetch. Returns an empty\n * array when the token has no pool yet or the subgraph is unreachable,\n * so the caller can show a \"quote unavailable\" state without crashing.\n *\n * @param chainId - Chain ID (used to select the subgraph endpoint)\n * @param pointTokenAddress - The PointToken contract address\n * @param subgraphUrl - Override the default PAFI subgraph URL\n */\nexport async function fetchPafiPools(\n _chainId: number,\n pointTokenAddress: Address,\n subgraphUrl: string = PAFI_SUBGRAPH_URL,\n): Promise<PoolKey[]> {\n let response: Response;\n try {\n response = await fetch(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: POOL_QUERY,\n variables: { id: pointTokenAddress.toLowerCase() },\n }),\n });\n } catch (err) {\n console.warn(\"[fetchPafiPools] subgraph unreachable:\", (err as Error).message);\n return [];\n }\n\n if (!response.ok) {\n console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);\n return [];\n }\n\n const json = (await response.json()) as GraphQLResponse;\n if (json.errors && json.errors.length > 0) {\n console.warn(\"[fetchPafiPools] subgraph errors:\", json.errors.map((e) => e.message).join(\"; \"));\n return [];\n }\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) return [];\n\n if (\n !isAddress(pool.hooks) ||\n !isAddress(pool.token0.id) ||\n !isAddress(pool.token1.id) ||\n !Number.isFinite(Number(pool.feeTier)) ||\n !Number.isFinite(Number(pool.tickSpacing))\n ) {\n console.error(\"[fetchPafiPools] invalid pool data in subgraph response — skipping\");\n return [];\n }\n\n const [currency0, currency1] = sortCurrencies(\n pool.token0.id as Address,\n pool.token1.id as Address,\n );\n\n return [{\n currency0,\n currency1,\n fee: Number(pool.feeTier),\n tickSpacing: Number(pool.tickSpacing),\n hooks: pool.hooks as Address,\n }];\n}\n"],"mappings":"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","names":["import_viem"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api/handlers.ts","../src/pools.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\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"./pools\";\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","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Address, PublicClient } from 'viem';
|
|
2
2
|
import { PartialUserOperation, PoolKey } from '@pafi-dev/core';
|
|
3
|
+
export { PAFI_SUBGRAPH_URL, fetchPafiPools } from '@pafi-dev/core';
|
|
3
4
|
|
|
4
5
|
interface ApiQuoteRequest {
|
|
5
6
|
chainId: number;
|
|
@@ -137,18 +138,4 @@ declare class TradingHandlers {
|
|
|
137
138
|
handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Fetch the Uniswap V4 pool(s) for a PAFI PointToken from the subgraph.
|
|
143
|
-
*
|
|
144
|
-
* Browser and Node compatible — uses globalThis.fetch. Returns an empty
|
|
145
|
-
* array when the token has no pool yet or the subgraph is unreachable,
|
|
146
|
-
* so the caller can show a "quote unavailable" state without crashing.
|
|
147
|
-
*
|
|
148
|
-
* @param chainId - Chain ID (used to select the subgraph endpoint)
|
|
149
|
-
* @param pointTokenAddress - The PointToken contract address
|
|
150
|
-
* @param subgraphUrl - Override the default PAFI subgraph URL
|
|
151
|
-
*/
|
|
152
|
-
declare function fetchPafiPools(_chainId: number, pointTokenAddress: Address, subgraphUrl?: string): Promise<PoolKey[]>;
|
|
153
|
-
|
|
154
|
-
export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, PAFI_SUBGRAPH_URL, TradingHandlers, type TradingHandlersConfig, fetchPafiPools };
|
|
141
|
+
export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, TradingHandlers, type TradingHandlersConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Address, PublicClient } from 'viem';
|
|
2
2
|
import { PartialUserOperation, PoolKey } from '@pafi-dev/core';
|
|
3
|
+
export { PAFI_SUBGRAPH_URL, fetchPafiPools } from '@pafi-dev/core';
|
|
3
4
|
|
|
4
5
|
interface ApiQuoteRequest {
|
|
5
6
|
chainId: number;
|
|
@@ -137,18 +138,4 @@ declare class TradingHandlers {
|
|
|
137
138
|
handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Fetch the Uniswap V4 pool(s) for a PAFI PointToken from the subgraph.
|
|
143
|
-
*
|
|
144
|
-
* Browser and Node compatible — uses globalThis.fetch. Returns an empty
|
|
145
|
-
* array when the token has no pool yet or the subgraph is unreachable,
|
|
146
|
-
* so the caller can show a "quote unavailable" state without crashing.
|
|
147
|
-
*
|
|
148
|
-
* @param chainId - Chain ID (used to select the subgraph endpoint)
|
|
149
|
-
* @param pointTokenAddress - The PointToken contract address
|
|
150
|
-
* @param subgraphUrl - Override the default PAFI subgraph URL
|
|
151
|
-
*/
|
|
152
|
-
declare function fetchPafiPools(_chainId: number, pointTokenAddress: Address, subgraphUrl?: string): Promise<PoolKey[]>;
|
|
153
|
-
|
|
154
|
-
export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, PAFI_SUBGRAPH_URL, TradingHandlers, type TradingHandlersConfig, fetchPafiPools };
|
|
141
|
+
export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, TradingHandlers, type TradingHandlersConfig };
|
package/dist/index.js
CHANGED
|
@@ -200,68 +200,7 @@ var TradingHandlers = class {
|
|
|
200
200
|
};
|
|
201
201
|
|
|
202
202
|
// src/pools.ts
|
|
203
|
-
import {
|
|
204
|
-
var PAFI_SUBGRAPH_URL = "https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v2";
|
|
205
|
-
var POOL_QUERY = `
|
|
206
|
-
query GetPoolForPointToken($id: ID!) {
|
|
207
|
-
pafiToken(id: $id) {
|
|
208
|
-
id
|
|
209
|
-
pool {
|
|
210
|
-
id
|
|
211
|
-
feeTier
|
|
212
|
-
tickSpacing
|
|
213
|
-
hooks
|
|
214
|
-
token0 { id }
|
|
215
|
-
token1 { id }
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
`;
|
|
220
|
-
function sortCurrencies(a, b) {
|
|
221
|
-
return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
|
|
222
|
-
}
|
|
223
|
-
async function fetchPafiPools(_chainId, pointTokenAddress, subgraphUrl = PAFI_SUBGRAPH_URL) {
|
|
224
|
-
let response;
|
|
225
|
-
try {
|
|
226
|
-
response = await fetch(subgraphUrl, {
|
|
227
|
-
method: "POST",
|
|
228
|
-
headers: { "Content-Type": "application/json" },
|
|
229
|
-
body: JSON.stringify({
|
|
230
|
-
query: POOL_QUERY,
|
|
231
|
-
variables: { id: pointTokenAddress.toLowerCase() }
|
|
232
|
-
})
|
|
233
|
-
});
|
|
234
|
-
} catch (err) {
|
|
235
|
-
console.warn("[fetchPafiPools] subgraph unreachable:", err.message);
|
|
236
|
-
return [];
|
|
237
|
-
}
|
|
238
|
-
if (!response.ok) {
|
|
239
|
-
console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
const json = await response.json();
|
|
243
|
-
if (json.errors && json.errors.length > 0) {
|
|
244
|
-
console.warn("[fetchPafiPools] subgraph errors:", json.errors.map((e) => e.message).join("; "));
|
|
245
|
-
return [];
|
|
246
|
-
}
|
|
247
|
-
const pool = json.data?.pafiToken?.pool;
|
|
248
|
-
if (!pool) return [];
|
|
249
|
-
if (!isAddress(pool.hooks) || !isAddress(pool.token0.id) || !isAddress(pool.token1.id) || !Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
|
|
250
|
-
console.error("[fetchPafiPools] invalid pool data in subgraph response \u2014 skipping");
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
const [currency0, currency1] = sortCurrencies(
|
|
254
|
-
pool.token0.id,
|
|
255
|
-
pool.token1.id
|
|
256
|
-
);
|
|
257
|
-
return [{
|
|
258
|
-
currency0,
|
|
259
|
-
currency1,
|
|
260
|
-
fee: Number(pool.feeTier),
|
|
261
|
-
tickSpacing: Number(pool.tickSpacing),
|
|
262
|
-
hooks: pool.hooks
|
|
263
|
-
}];
|
|
264
|
-
}
|
|
203
|
+
import { fetchPafiPools, PAFI_SUBGRAPH_URL } from "@pafi-dev/core";
|
|
265
204
|
export {
|
|
266
205
|
PAFI_SUBGRAPH_URL,
|
|
267
206
|
TradingHandlers,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/handlers.ts","../src/pools.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","import { isAddress } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { PoolKey } from \"@pafi-dev/core\";\n\nexport const PAFI_SUBGRAPH_URL =\n \"https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v2\";\n\nconst POOL_QUERY = `\n query GetPoolForPointToken($id: ID!) {\n pafiToken(id: $id) {\n id\n pool {\n id\n feeTier\n tickSpacing\n hooks\n token0 { id }\n token1 { id }\n }\n }\n }\n`;\n\ninterface GraphQLResponse {\n data?: {\n pafiToken: {\n id: string;\n pool: {\n id: string;\n feeTier: string;\n tickSpacing: string;\n hooks: string;\n token0: { id: string };\n token1: { id: string };\n } | null;\n } | null;\n };\n errors?: { message: string }[];\n}\n\nfunction sortCurrencies(a: Address, b: Address): [Address, Address] {\n return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];\n}\n\n/**\n * Fetch the Uniswap V4 pool(s) for a PAFI PointToken from the subgraph.\n *\n * Browser and Node compatible — uses globalThis.fetch. Returns an empty\n * array when the token has no pool yet or the subgraph is unreachable,\n * so the caller can show a \"quote unavailable\" state without crashing.\n *\n * @param chainId - Chain ID (used to select the subgraph endpoint)\n * @param pointTokenAddress - The PointToken contract address\n * @param subgraphUrl - Override the default PAFI subgraph URL\n */\nexport async function fetchPafiPools(\n _chainId: number,\n pointTokenAddress: Address,\n subgraphUrl: string = PAFI_SUBGRAPH_URL,\n): Promise<PoolKey[]> {\n let response: Response;\n try {\n response = await fetch(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: POOL_QUERY,\n variables: { id: pointTokenAddress.toLowerCase() },\n }),\n });\n } catch (err) {\n console.warn(\"[fetchPafiPools] subgraph unreachable:\", (err as Error).message);\n return [];\n }\n\n if (!response.ok) {\n console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);\n return [];\n }\n\n const json = (await response.json()) as GraphQLResponse;\n if (json.errors && json.errors.length > 0) {\n console.warn(\"[fetchPafiPools] subgraph errors:\", json.errors.map((e) => e.message).join(\"; \"));\n return [];\n }\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) return [];\n\n if (\n !isAddress(pool.hooks) ||\n !isAddress(pool.token0.id) ||\n !isAddress(pool.token1.id) ||\n !Number.isFinite(Number(pool.feeTier)) ||\n !Number.isFinite(Number(pool.tickSpacing))\n ) {\n console.error(\"[fetchPafiPools] invalid pool data in subgraph response — skipping\");\n return [];\n }\n\n const [currency0, currency1] = sortCurrencies(\n pool.token0.id as Address,\n pool.token1.id as Address,\n );\n\n return [{\n currency0,\n currency1,\n fee: Number(pool.feeTier),\n tickSpacing: Number(pool.tickSpacing),\n hooks: pool.hooks as Address,\n }];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/api/handlers.ts","../src/pools.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","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pafi-dev/trading",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Stateless on-chain trading handlers for PAFI — swap, quote, perp deposit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@pafi-dev/core": "0.5.
|
|
25
|
+
"@pafi-dev/core": "0.5.17"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"viem": "^2.0.0"
|