@varla/sdk 3.2.0 → 4.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.
Files changed (45) hide show
  1. package/AGENTS.md +9 -9
  2. package/CHANGELOG.md +35 -0
  3. package/dist/abi/full/VarlaCore.d.ts +44 -60
  4. package/dist/abi/full/VarlaCore.d.ts.map +1 -1
  5. package/dist/abi/full/VarlaCore.js +55 -77
  6. package/dist/abi/full/VarlaCore.js.map +1 -1
  7. package/dist/abi/full/VarlaLiquidator.d.ts +1 -1
  8. package/dist/abi/full/VarlaLiquidator.js +1 -1
  9. package/dist/abi/full/VarlaLiquidator.js.map +1 -1
  10. package/dist/actions/core.d.ts +35 -0
  11. package/dist/actions/core.d.ts.map +1 -1
  12. package/dist/actions/core.js +36 -5
  13. package/dist/actions/core.js.map +1 -1
  14. package/dist/actions/erc1155.d.ts +73 -0
  15. package/dist/actions/erc1155.d.ts.map +1 -1
  16. package/dist/actions/erc1155.js +74 -0
  17. package/dist/actions/erc1155.js.map +1 -1
  18. package/dist/addresses/polygon.js +11 -11
  19. package/dist/addresses/polygonProxy.js +6 -6
  20. package/dist/generated.d.ts +45 -61
  21. package/dist/generated.d.ts.map +1 -1
  22. package/dist/leverage/deleverageExecute.d.ts +7 -1
  23. package/dist/leverage/deleverageExecute.d.ts.map +1 -1
  24. package/dist/leverage/deleverageExecute.js +11 -16
  25. package/dist/leverage/deleverageExecute.js.map +1 -1
  26. package/dist/leverage/execute.d.ts +7 -1
  27. package/dist/leverage/execute.d.ts.map +1 -1
  28. package/dist/leverage/execute.js +11 -16
  29. package/dist/leverage/execute.js.map +1 -1
  30. package/dist/views/core.d.ts +81 -1
  31. package/dist/views/core.d.ts.map +1 -1
  32. package/dist/views/core.js +81 -2
  33. package/dist/views/core.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/abi/full/VarlaCore.ts +55 -77
  36. package/src/abi/full/VarlaLiquidator.ts +1 -1
  37. package/src/actions/core.ts +51 -5
  38. package/src/actions/erc1155.ts +96 -0
  39. package/src/addresses/polygon.json +11 -11
  40. package/src/addresses/polygon.ts +11 -11
  41. package/src/addresses/polygonProxy.json +6 -6
  42. package/src/addresses/polygonProxy.ts +6 -6
  43. package/src/leverage/deleverageExecute.ts +19 -20
  44. package/src/leverage/execute.ts +19 -20
  45. package/src/views/core.ts +153 -3
@@ -5,6 +5,8 @@ import type { Address, PublicClient } from "viem";
5
5
  import { abis } from "../generated.js";
6
6
  import type { SimulatedTx } from "./oracle.js";
7
7
 
8
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" as const;
9
+
8
10
  export async function prepareCoreDeposit(params: {
9
11
  publicClient: Pick<PublicClient, "simulateContract">;
10
12
  coreAddress: Address;
@@ -21,18 +23,50 @@ export async function prepareCoreDeposit(params: {
21
23
  } as any)) as any;
22
24
  }
23
25
 
26
+ /**
27
+ * Withdraw deposited ERC1155 collateral from VarlaCore back to a wallet.
28
+ *
29
+ * Two call paths:
30
+ * - **No `recipient`** (default): calls `withdraw(ids, amounts)` — tokens sent to `msg.sender`.
31
+ * - **With `recipient`**: calls `withdraw(ids, amounts, destination)` — tokens sent to the
32
+ * specified address instead of the caller. Useful for Safe / proxy wallets or any flow
33
+ * where the signer and the token recipient differ.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // Withdraw to self (default)
38
+ * const tx = await prepareCoreWithdraw({ publicClient, coreAddress, account, positionIds, amounts });
39
+ *
40
+ * // Withdraw to a different address
41
+ * const tx = await prepareCoreWithdraw({
42
+ * publicClient, coreAddress, account,
43
+ * positionIds, amounts,
44
+ * recipient: "0xAbc...",
45
+ * });
46
+ * ```
47
+ */
24
48
  export async function prepareCoreWithdraw(params: {
25
49
  publicClient: Pick<PublicClient, "simulateContract">;
26
50
  coreAddress: Address;
27
51
  account: Address;
28
52
  positionIds: readonly bigint[];
29
53
  amounts: readonly bigint[];
54
+ /**
55
+ * Optional destination address for the withdrawn ERC1155 tokens.
56
+ *
57
+ * When provided, calls `withdraw(positionIds, amounts, destination)`.
58
+ * When omitted, calls `withdraw(positionIds, amounts)` (tokens go to `msg.sender`).
59
+ */
60
+ recipient?: Address;
30
61
  }): Promise<SimulatedTx> {
31
62
  return (await params.publicClient.simulateContract({
32
63
  address: params.coreAddress,
33
64
  abi: abis.VARLACORE_ABI,
34
65
  functionName: "withdraw",
35
- args: [params.positionIds, params.amounts],
66
+ args:
67
+ params.recipient !== undefined
68
+ ? [params.positionIds, params.amounts, params.recipient]
69
+ : [params.positionIds, params.amounts],
36
70
  account: params.account,
37
71
  } as any)) as any;
38
72
  }
@@ -42,12 +76,17 @@ export async function prepareCoreBorrow(params: {
42
76
  coreAddress: Address;
43
77
  account: Address;
44
78
  amount: bigint;
79
+ outputAsset?: Address;
80
+ recipient?: Address;
45
81
  }): Promise<SimulatedTx> {
82
+ const useRecipientPath = params.outputAsset !== undefined || params.recipient !== undefined;
46
83
  return (await params.publicClient.simulateContract({
47
84
  address: params.coreAddress,
48
85
  abi: abis.VARLACORE_ABI,
49
86
  functionName: "borrow",
50
- args: [params.amount],
87
+ args: useRecipientPath
88
+ ? [params.amount, params.outputAsset ?? ZERO_ADDRESS, params.recipient ?? params.account]
89
+ : [params.amount],
51
90
  account: params.account,
52
91
  } as any)) as any;
53
92
  }
@@ -58,12 +97,13 @@ export async function prepareCoreBorrowWithAsset(params: {
58
97
  account: Address;
59
98
  amount: bigint;
60
99
  outputAsset: Address;
100
+ recipient?: Address;
61
101
  }): Promise<SimulatedTx> {
62
102
  return (await params.publicClient.simulateContract({
63
103
  address: params.coreAddress,
64
104
  abi: abis.VARLACORE_ABI,
65
105
  functionName: "borrow",
66
- args: [params.amount, params.outputAsset],
106
+ args: [params.amount, params.outputAsset, params.recipient ?? params.account],
67
107
  account: params.account,
68
108
  } as any)) as any;
69
109
  }
@@ -73,12 +113,17 @@ export async function prepareCoreRepay(params: {
73
113
  coreAddress: Address;
74
114
  account: Address;
75
115
  amount: bigint;
116
+ inputAsset?: Address;
117
+ onBehalfOf?: Address;
76
118
  }): Promise<SimulatedTx> {
119
+ const useOnBehalfPath = params.inputAsset !== undefined || params.onBehalfOf !== undefined;
77
120
  return (await params.publicClient.simulateContract({
78
121
  address: params.coreAddress,
79
122
  abi: abis.VARLACORE_ABI,
80
123
  functionName: "repay",
81
- args: [params.amount],
124
+ args: useOnBehalfPath
125
+ ? [params.amount, params.inputAsset ?? ZERO_ADDRESS, params.onBehalfOf ?? params.account]
126
+ : [params.amount],
82
127
  account: params.account,
83
128
  } as any)) as any;
84
129
  }
@@ -89,12 +134,13 @@ export async function prepareCoreRepayWithAsset(params: {
89
134
  account: Address;
90
135
  amount: bigint;
91
136
  inputAsset: Address;
137
+ onBehalfOf?: Address;
92
138
  }): Promise<SimulatedTx> {
93
139
  return (await params.publicClient.simulateContract({
94
140
  address: params.coreAddress,
95
141
  abi: abis.VARLACORE_ABI,
96
142
  functionName: "repay",
97
- args: [params.amount, params.inputAsset],
143
+ args: [params.amount, params.inputAsset, params.onBehalfOf ?? params.account],
98
144
  account: params.account,
99
145
  } as any)) as any;
100
146
  }
@@ -20,3 +20,99 @@ export async function prepareErc1155SetApprovalForAll(params: {
20
20
  account: params.account,
21
21
  } as any)) as any;
22
22
  }
23
+
24
+ /**
25
+ * Deposit a single ERC1155 position into VarlaCore by directly calling
26
+ * `ctf.safeTransferFrom(user, core, id, amount, "")`.
27
+ *
28
+ * This triggers VarlaCore's `onERC1155Received` hook which credits the
29
+ * sender (`from`) with the deposited amount — no prior `setApprovalForAll`
30
+ * to Core is required since the caller is initiating the transfer themselves.
31
+ *
32
+ * Use this as a drop-in alternative to `prepareCoreDeposit` when the user
33
+ * prefers the ERC1155-push pattern (e.g. in single-token flows or when
34
+ * building atomic Safe multisend batches where approval is undesirable).
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { prepareCoreDepositViaSafeTransfer } from "@varla/sdk/actions";
39
+ *
40
+ * const tx = await prepareCoreDepositViaSafeTransfer({
41
+ * publicClient,
42
+ * ctfAddress,
43
+ * coreAddress,
44
+ * account: userAddress,
45
+ * positionId: BigInt(posId),
46
+ * amount: 100_000_000n, // 100 USDC (6 decimals)
47
+ * });
48
+ * await walletClient.writeContract(tx.request);
49
+ * ```
50
+ */
51
+ export async function prepareCoreDepositViaSafeTransfer(params: {
52
+ publicClient: Pick<PublicClient, "simulateContract">;
53
+ /** Address of the ERC1155 positions token (Polymarket CTF). */
54
+ ctfAddress: Address;
55
+ /** Address of VarlaCore (the transfer recipient). */
56
+ coreAddress: Address;
57
+ account: Address;
58
+ positionId: bigint;
59
+ amount: bigint;
60
+ }): Promise<SimulatedTx> {
61
+ return (await params.publicClient.simulateContract({
62
+ address: params.ctfAddress,
63
+ abi: erc1155Abi,
64
+ functionName: "safeTransferFrom",
65
+ args: [params.account, params.coreAddress, params.positionId, params.amount, "0x"],
66
+ account: params.account,
67
+ } as any)) as any;
68
+ }
69
+
70
+ /**
71
+ * Deposit multiple ERC1155 positions into VarlaCore in a single call by using
72
+ * `ctf.safeBatchTransferFrom(user, core, ids, amounts, "")`.
73
+ *
74
+ * This triggers VarlaCore's `onERC1155BatchReceived` hook which credits the
75
+ * sender (`from`) with each transferred amount. No prior `setApprovalForAll`
76
+ * to Core is needed.
77
+ *
78
+ * Prefer this over multiple `prepareCoreDepositViaSafeTransfer` calls when
79
+ * depositing several positions atomically (single tx, lower gas).
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * import { prepareCoreDepositViaSafeBatchTransfer } from "@varla/sdk/actions";
84
+ *
85
+ * const tx = await prepareCoreDepositViaSafeBatchTransfer({
86
+ * publicClient,
87
+ * ctfAddress,
88
+ * coreAddress,
89
+ * account: userAddress,
90
+ * positionIds: [BigInt(yesId), BigInt(noId)],
91
+ * amounts: [50_000_000n, 50_000_000n],
92
+ * });
93
+ * await walletClient.writeContract(tx.request);
94
+ * ```
95
+ */
96
+ export async function prepareCoreDepositViaSafeBatchTransfer(params: {
97
+ publicClient: Pick<PublicClient, "simulateContract">;
98
+ /** Address of the ERC1155 positions token (Polymarket CTF). */
99
+ ctfAddress: Address;
100
+ /** Address of VarlaCore (the transfer recipient). */
101
+ coreAddress: Address;
102
+ account: Address;
103
+ positionIds: readonly bigint[];
104
+ amounts: readonly bigint[];
105
+ }): Promise<SimulatedTx> {
106
+ if (params.positionIds.length !== params.amounts.length) {
107
+ throw new Error(
108
+ `prepareCoreDepositViaSafeBatchTransfer: positionIds.length (${params.positionIds.length}) must equal amounts.length (${params.amounts.length})`,
109
+ );
110
+ }
111
+ return (await params.publicClient.simulateContract({
112
+ address: params.ctfAddress,
113
+ abi: erc1155Abi,
114
+ functionName: "safeBatchTransferFrom",
115
+ args: [params.account, params.coreAddress, params.positionIds, params.amounts, "0x"],
116
+ account: params.account,
117
+ } as any)) as any;
118
+ }
@@ -3,16 +3,16 @@
3
3
  "generatedAt": "",
4
4
  "deploymentsDir": "polygon",
5
5
  "contracts": {
6
- "VarlaAccessManager": "0x9d880933Def30A33f706dC2c1882a66598d2e891",
7
- "VarlaOracle": "0x4E3d7d4264579473FDC2a40db15b42e8CA27CE4f",
8
- "VarlaCore": "0x7bD415806e99e852B92fb55cD830F55e7F026C40",
9
- "VarlaPool": "0x0B51D2497EdceCF5eedDB16ed6E12270410E18fE",
10
- "OracleUpdaterRouter": "0x0e298855e9D06c0a05Efe13163e8D6bfD98ea8BA",
11
- "VarlaInterestRateStrategy": "0xA0228e70AdaEb6E64037ca74d033f26d61e16F91",
12
- "VarlaProxyAdmin": "0xAF6a35C861f377060a40B14aD18fd167b6AF6d3C",
13
- "VarlaLiquidator": "0xacd5FDebE867536703bbC18e6b49fA6053179cf7",
14
- "VarlaMergeLiquidator": "0xFfAF752671360cb6baFc7290155e8a81109d4589",
15
- "VarlaConvertLiquidator": "0xE8507e011dAC6Bf07e742AF39e16deC647a70746",
16
- "PolymarketCtfAdapter": "0xEbB92A5374D277A3e1CA915741ce3077364d95c0"
6
+ "VarlaAccessManager": "0xEC4CBe73cFA6846B6772341e199289F7A4f20e7e",
7
+ "VarlaOracle": "0xA63c6ebD3a0C6990D16Ea94D957c2Ad8D3c7Dd10",
8
+ "VarlaCore": "0xc1c7720Eb3563630aA77be561cD0B58F1Fa280F6",
9
+ "VarlaPool": "0xc939CC89340E22bDf33df82A55118b19d4A11Bc2",
10
+ "OracleUpdaterRouter": "0x59389217DEB847b3747767b2D7f56ffec43E9c72",
11
+ "VarlaInterestRateStrategy": "0x62cf2ab7346b5bA86faaa302d1E39908525166dB",
12
+ "VarlaProxyAdmin": "0xf06B99237d2A53c6026ff5E3723417E6dEDcA651",
13
+ "VarlaLiquidator": "0xd7760D42661b8A559A61351873E0e006985F0d75",
14
+ "VarlaMergeLiquidator": "0x5fA44Ba97470ADaDc97cb7F3ce64baceeE37f97a",
15
+ "VarlaConvertLiquidator": "0xBA802DF5e8258636738A28d27573203B32D2120A",
16
+ "PolymarketCtfAdapter": "0x66Ed3cf23719eb9111966AF6D6842F6548165313"
17
17
  }
18
18
  }
@@ -2,16 +2,16 @@
2
2
  import type { AddressBook } from "../types";
3
3
 
4
4
  export const polygon: AddressBook = {
5
- accessManager: "0x9d880933Def30A33f706dC2c1882a66598d2e891",
6
- oracle: "0x4E3d7d4264579473FDC2a40db15b42e8CA27CE4f",
7
- core: "0x7bD415806e99e852B92fb55cD830F55e7F026C40",
8
- pool: "0x0B51D2497EdceCF5eedDB16ed6E12270410E18fE",
9
- oracleUpdaterRouter: "0x0e298855e9D06c0a05Efe13163e8D6bfD98ea8BA",
10
- interestRateStrategy: "0xA0228e70AdaEb6E64037ca74d033f26d61e16F91",
11
- proxyAdmin: "0xAF6a35C861f377060a40B14aD18fd167b6AF6d3C",
12
- liquidator: "0xacd5FDebE867536703bbC18e6b49fA6053179cf7",
13
- mergeLiquidator: "0xFfAF752671360cb6baFc7290155e8a81109d4589",
14
- convertLiquidator: "0xE8507e011dAC6Bf07e742AF39e16deC647a70746",
15
- polymarketCtfAdapter: "0xEbB92A5374D277A3e1CA915741ce3077364d95c0",
5
+ accessManager: "0xEC4CBe73cFA6846B6772341e199289F7A4f20e7e",
6
+ oracle: "0xA63c6ebD3a0C6990D16Ea94D957c2Ad8D3c7Dd10",
7
+ core: "0xc1c7720Eb3563630aA77be561cD0B58F1Fa280F6",
8
+ pool: "0xc939CC89340E22bDf33df82A55118b19d4A11Bc2",
9
+ oracleUpdaterRouter: "0x59389217DEB847b3747767b2D7f56ffec43E9c72",
10
+ interestRateStrategy: "0x62cf2ab7346b5bA86faaa302d1E39908525166dB",
11
+ proxyAdmin: "0xf06B99237d2A53c6026ff5E3723417E6dEDcA651",
12
+ liquidator: "0xd7760D42661b8A559A61351873E0e006985F0d75",
13
+ mergeLiquidator: "0x5fA44Ba97470ADaDc97cb7F3ce64baceeE37f97a",
14
+ convertLiquidator: "0xBA802DF5e8258636738A28d27573203B32D2120A",
15
+ polymarketCtfAdapter: "0x66Ed3cf23719eb9111966AF6D6842F6548165313",
16
16
  };
17
17
 
@@ -5,14 +5,14 @@
5
5
  "contracts": {
6
6
  "VarlaAccessManager": "0x5069d899bA3EBC4e12aB357F44B1DA3b0D0395f4",
7
7
  "VarlaOracle": "0x14e102a52474f569eB6C536a1bb15b9F69c5ba8f",
8
- "VarlaCore": "0xb4b6887eBFFF5532a1fcED5A1B30F4Cf37aBf78f",
9
- "VarlaPool": "0xB5f6F3F547c4CcedF92007B7d05C765aaA998497",
8
+ "VarlaCore": "0xB7e45923a4B21793Bae7Bd445fdd7318B0fF1409",
9
+ "VarlaPool": "0x0845B5b2cF295C77eBB23347706A30448bEf9aF2",
10
10
  "OracleUpdaterRouter": "0x3E1e6B54626D486b844C94e2385773b018fEB891",
11
- "VarlaInterestRateStrategy": "0x80B990D9D637DEe4781A79da1CA9D36Bb3C949f6",
11
+ "VarlaInterestRateStrategy": "0x3Ae3a0B358a53aF835A924F4799c91943206e3d1",
12
12
  "VarlaProxyAdmin": "0x084aBfA2f32A53741DDd12D3166dA1318e7d73f7",
13
- "VarlaLiquidator": "0x91949971c5B6F0783dC90F86938B7244215A7311",
14
- "VarlaMergeLiquidator": "0x3F86802a7823C2d2D8bBBC3C7208289AfD9aCD96",
15
- "VarlaConvertLiquidator": "0xdc0DDd1a58C21f84fA7FCBdA56e8a6f012e4fC43",
13
+ "VarlaLiquidator": "0x0f031BFbfFB08C3f844b8e59BAd9975CBec194b5",
14
+ "VarlaMergeLiquidator": "0x573A7bD3117c9D8EA1913c2Ccbdbf29E86EE4621",
15
+ "VarlaConvertLiquidator": "0xc1ba05218Fb56b5e31D34b3bb772fa57F1eDC7e4",
16
16
  "PolymarketCtfAdapter": "0x86B39800540D2C42eF1E34DF69117CB34F35a341"
17
17
  }
18
18
  }
@@ -4,14 +4,14 @@ import type { AddressBook } from "../types";
4
4
  export const polygonProxy: AddressBook = {
5
5
  accessManager: "0x5069d899bA3EBC4e12aB357F44B1DA3b0D0395f4",
6
6
  oracle: "0x14e102a52474f569eB6C536a1bb15b9F69c5ba8f",
7
- core: "0xb4b6887eBFFF5532a1fcED5A1B30F4Cf37aBf78f",
8
- pool: "0xB5f6F3F547c4CcedF92007B7d05C765aaA998497",
7
+ core: "0xB7e45923a4B21793Bae7Bd445fdd7318B0fF1409",
8
+ pool: "0x0845B5b2cF295C77eBB23347706A30448bEf9aF2",
9
9
  oracleUpdaterRouter: "0x3E1e6B54626D486b844C94e2385773b018fEB891",
10
- interestRateStrategy: "0x80B990D9D637DEe4781A79da1CA9D36Bb3C949f6",
10
+ interestRateStrategy: "0x3Ae3a0B358a53aF835A924F4799c91943206e3d1",
11
11
  proxyAdmin: "0x084aBfA2f32A53741DDd12D3166dA1318e7d73f7",
12
- liquidator: "0x91949971c5B6F0783dC90F86938B7244215A7311",
13
- mergeLiquidator: "0x3F86802a7823C2d2D8bBBC3C7208289AfD9aCD96",
14
- convertLiquidator: "0xdc0DDd1a58C21f84fA7FCBdA56e8a6f012e4fC43",
12
+ liquidator: "0x0f031BFbfFB08C3f844b8e59BAd9975CBec194b5",
13
+ mergeLiquidator: "0x573A7bD3117c9D8EA1913c2Ccbdbf29E86EE4621",
14
+ convertLiquidator: "0xc1ba05218Fb56b5e31D34b3bb772fa57F1eDC7e4",
15
15
  polymarketCtfAdapter: "0x86B39800540D2C42eF1E34DF69117CB34F35a341",
16
16
  };
17
17
 
@@ -9,11 +9,7 @@
9
9
 
10
10
  import type { Address, Hash, PublicClient, WalletClient } from "viem";
11
11
 
12
- import {
13
- prepareCoreRepay,
14
- prepareCoreRepayWithAsset,
15
- prepareCoreWithdraw,
16
- } from "../actions/core.js";
12
+ import { prepareCoreRepay, prepareCoreWithdraw } from "../actions/core.js";
17
13
  import { sendTx } from "../actions/tx.js";
18
14
  import type { ReceiptRetryOptions } from "./receipt.js";
19
15
  import { waitForReceiptWithRetry } from "./receipt.js";
@@ -39,10 +35,16 @@ export type RunDeleverageLoopParams = {
39
35
  /**
40
36
  * Optional repay settlement asset for core repay steps.
41
37
  *
42
- * When provided, repay steps call `VarlaCore.repay(amount, inputAsset)`.
38
+ * When provided, repay steps call `VarlaCore.repay(amount, inputAsset, onBehalfOf)`.
43
39
  * When omitted, repay steps call the default `VarlaCore.repay(amount)`.
44
40
  */
45
41
  inputAsset?: Address;
42
+ /**
43
+ * Optional borrower whose debt should be repaid by `account`.
44
+ *
45
+ * Defaults to `account`.
46
+ */
47
+ repayOnBehalfOf?: Address;
46
48
  /** Off-chain sell callback. */
47
49
  onSellStep: (step: Extract<DeleverageStep, { kind: "sell" }>) => Promise<SellResult>;
48
50
  onProgress?: OnDeleverageProgress;
@@ -139,6 +141,7 @@ export async function runDeleverageLoop(
139
141
  coreAddress,
140
142
  account,
141
143
  inputAsset,
144
+ repayOnBehalfOf,
142
145
  onSellStep,
143
146
  onProgress,
144
147
  confirmations = 1,
@@ -207,21 +210,17 @@ export async function runDeleverageLoop(
207
210
  if (step.kind === "repay") {
208
211
  const repayAmount = lastSellActualCollateral ?? step.collateralAmount;
209
212
  lastSellActualCollateral = undefined;
213
+ const onBehalfOf = repayOnBehalfOf ?? account;
214
+ const useOnBehalfPath =
215
+ inputAsset !== undefined || onBehalfOf.toLowerCase() !== account.toLowerCase();
210
216
 
211
- const sim = inputAsset
212
- ? await prepareCoreRepayWithAsset({
213
- publicClient: publicClient as any,
214
- coreAddress,
215
- account,
216
- amount: repayAmount,
217
- inputAsset,
218
- })
219
- : await prepareCoreRepay({
220
- publicClient: publicClient as any,
221
- coreAddress,
222
- account,
223
- amount: repayAmount,
224
- });
217
+ const sim = await prepareCoreRepay({
218
+ publicClient: publicClient as any,
219
+ coreAddress,
220
+ account,
221
+ amount: repayAmount,
222
+ ...(useOnBehalfPath ? { inputAsset, onBehalfOf } : {}),
223
+ });
225
224
  const hash = await sendTx({ walletClient: walletClient as any, request: sim.request });
226
225
  await waitForReceiptWithRetry({ publicClient, hash, confirmations, retry: receiptRetry });
227
226
 
@@ -10,11 +10,7 @@
10
10
 
11
11
  import type { Address, Hash, PublicClient, WalletClient } from "viem";
12
12
 
13
- import {
14
- prepareCoreBorrow,
15
- prepareCoreBorrowWithAsset,
16
- prepareCoreDeposit,
17
- } from "../actions/core.js";
13
+ import { prepareCoreBorrow, prepareCoreDeposit } from "../actions/core.js";
18
14
  import { sendTx } from "../actions/tx.js";
19
15
  import type { ReceiptRetryOptions } from "./receipt.js";
20
16
  import { waitForReceiptWithRetry } from "./receipt.js";
@@ -44,10 +40,16 @@ export type RunLeverageLoopParams = {
44
40
  /**
45
41
  * Optional borrow output asset for core borrow steps.
46
42
  *
47
- * When provided, borrow steps call `VarlaCore.borrow(amount, outputAsset)`.
43
+ * When provided, borrow steps call `VarlaCore.borrow(amount, outputAsset, recipient)`.
48
44
  * When omitted, borrow steps call the default `VarlaCore.borrow(amount)`.
49
45
  */
50
46
  outputAsset?: Address;
47
+ /**
48
+ * Optional recipient for borrow proceeds.
49
+ *
50
+ * Defaults to `account`. Debt remains on `account`.
51
+ */
52
+ borrowRecipient?: Address;
51
53
  /**
52
54
  * Consumer-provided callback for off-chain token acquisition.
53
55
  *
@@ -166,6 +168,7 @@ export async function runLeverageLoop(
166
168
  coreAddress,
167
169
  account,
168
170
  outputAsset,
171
+ borrowRecipient,
169
172
  onBuyStep,
170
173
  onProgress,
171
174
  confirmations = 1,
@@ -235,20 +238,16 @@ export async function runLeverageLoop(
235
238
  }
236
239
 
237
240
  if (step.kind === "borrow") {
238
- const sim = outputAsset
239
- ? await prepareCoreBorrowWithAsset({
240
- publicClient: publicClient as any,
241
- coreAddress,
242
- account,
243
- amount: step.collateralAmount,
244
- outputAsset,
245
- })
246
- : await prepareCoreBorrow({
247
- publicClient: publicClient as any,
248
- coreAddress,
249
- account,
250
- amount: step.collateralAmount,
251
- });
241
+ const recipient = borrowRecipient ?? account;
242
+ const useRecipientPath =
243
+ outputAsset !== undefined || recipient.toLowerCase() !== account.toLowerCase();
244
+ const sim = await prepareCoreBorrow({
245
+ publicClient: publicClient as any,
246
+ coreAddress,
247
+ account,
248
+ amount: step.collateralAmount,
249
+ ...(useRecipientPath ? { outputAsset, recipient } : {}),
250
+ });
252
251
 
253
252
  const hash = await sendTx({ walletClient: walletClient as any, request: sim.request });
254
253
  await waitForReceiptWithRetry({ publicClient, hash, confirmations, retry: receiptRetry });
package/src/views/core.ts CHANGED
@@ -196,6 +196,150 @@ export async function previewCoreDeposit(params: {
196
196
  };
197
197
  }
198
198
 
199
+ // ---------------------------------------------------------------------------
200
+ // Withdraw pre-flight (previewCoreWithdraw)
201
+ // ---------------------------------------------------------------------------
202
+
203
+ /**
204
+ * Reason a collateral withdrawal would be rejected for a specific position.
205
+ *
206
+ * - `"zero-amount"` — caller passed `amount === 0n`.
207
+ * - `"insufficient-balance"` — user's deposited balance is below the requested amount.
208
+ */
209
+ export type WithdrawBlockReason = "zero-amount" | "insufficient-balance";
210
+
211
+ export type PreviewCoreWithdrawItem = {
212
+ positionId: bigint;
213
+ amount: bigint;
214
+ /** Current deposited balance of the user for this position. */
215
+ balance: bigint;
216
+ /** `true` when this position passes the pre-flight balance check. */
217
+ ok: boolean;
218
+ /** Present only when `ok === false`. */
219
+ reason?: WithdrawBlockReason;
220
+ };
221
+
222
+ export type PreviewCoreWithdraw = {
223
+ /**
224
+ * `true` only when every position passes the balance check AND the user either
225
+ * has no outstanding debt or has sufficient health factor to absorb the withdrawal.
226
+ *
227
+ * When `hasDeb === true`, always simulate the on-chain call or check HF separately
228
+ * before submitting — this preview cannot compute the exact post-withdraw HF
229
+ * without oracle price data.
230
+ */
231
+ canWithdraw: boolean;
232
+ /** Per-position results aligned to input order. */
233
+ items: PreviewCoreWithdrawItem[];
234
+ /** `true` when the user has any outstanding debt. Withdrawing while indebted
235
+ * may reduce the health factor below the liquidation threshold. */
236
+ hasDebt: boolean;
237
+ /** Current health factor (WAD). `0` when the user has no debt. */
238
+ healthFactor: bigint;
239
+ };
240
+
241
+ /**
242
+ * Pre-flight check that mirrors VarlaCore.withdraw() balance validation.
243
+ *
244
+ * Checks (per position):
245
+ * 1. `amount > 0`
246
+ * 2. `core.positionBalances(user, id) >= amount`
247
+ *
248
+ * Account-level:
249
+ * - Whether the user currently has debt (`debt > 0`).
250
+ *
251
+ * **Important**: this function cannot predict the post-withdraw health factor
252
+ * without live oracle prices. When `hasDebt === true`, always double-check
253
+ * the HF with `readHealthFactor` or simulate the transaction before submitting.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * import { previewCoreWithdraw } from "@varla/sdk/views";
258
+ *
259
+ * const preview = await previewCoreWithdraw({
260
+ * core,
261
+ * user: "0x...",
262
+ * positionIds: [yesId],
263
+ * amounts: [50_000_000n],
264
+ * });
265
+ *
266
+ * if (!preview.canWithdraw) {
267
+ * for (const item of preview.items) {
268
+ * if (!item.ok) console.log(`Position ${item.positionId}: ${item.reason}`);
269
+ * }
270
+ * if (preview.hasDebt) console.log("User has debt — check HF before withdrawing");
271
+ * }
272
+ * ```
273
+ */
274
+ // wraps: VarlaCore.positionBalances,VarlaCore.getDebt,VarlaCore.getHealthFactor
275
+ export async function previewCoreWithdraw(params: {
276
+ core: {
277
+ read: {
278
+ positionBalances: (args: readonly [Address, bigint]) => Promise<bigint>;
279
+ getDebt: (args: readonly [Address]) => Promise<bigint>;
280
+ getHealthFactor: (args: readonly [Address]) => Promise<unknown>;
281
+ };
282
+ };
283
+ user: Address;
284
+ positionIds: readonly bigint[];
285
+ amounts: readonly bigint[];
286
+ }): Promise<PreviewCoreWithdraw> {
287
+ const { core, user, positionIds, amounts } = params;
288
+
289
+ if (positionIds.length !== amounts.length) {
290
+ throw new Error("previewCoreWithdraw: positionIds and amounts must have the same length");
291
+ }
292
+
293
+ // Fetch all balances and account debt in parallel.
294
+ const [balances, debt, hfRaw] = await Promise.all([
295
+ Promise.all(positionIds.map((pid) => core.read.positionBalances([user, pid]))),
296
+ core.read.getDebt([user]),
297
+ core.read.getHealthFactor([user]),
298
+ ]);
299
+
300
+ // Extract health factor from the tuple returned by VarlaCore.getHealthFactor.
301
+ const hfAny = hfRaw as any;
302
+ const healthFactor: bigint =
303
+ typeof hfAny === "bigint"
304
+ ? hfAny
305
+ : typeof hfAny?.healthFactor === "bigint"
306
+ ? hfAny.healthFactor
307
+ : (hfAny?.[0] ?? 0n);
308
+
309
+ // Per-position checks.
310
+ const items: PreviewCoreWithdrawItem[] = [];
311
+ let allItemsOk = true;
312
+
313
+ for (let i = 0; i < positionIds.length; i++) {
314
+ const pid = positionIds[i]!;
315
+ const amount = amounts[i]!;
316
+ const balance = balances[i]!;
317
+
318
+ let ok = true;
319
+ let reason: WithdrawBlockReason | undefined;
320
+
321
+ if (amount === 0n) {
322
+ ok = false;
323
+ reason = "zero-amount";
324
+ } else if (balance < amount) {
325
+ ok = false;
326
+ reason = "insufficient-balance";
327
+ }
328
+
329
+ if (!ok) allItemsOk = false;
330
+ items.push(
331
+ reason !== undefined
332
+ ? { positionId: pid, amount, balance, ok, reason }
333
+ : { positionId: pid, amount, balance, ok },
334
+ );
335
+ }
336
+
337
+ const hasDebt = debt > 0n;
338
+ const canWithdraw = allItemsOk;
339
+
340
+ return { canWithdraw, items, hasDebt, healthFactor };
341
+ }
342
+
199
343
  export type ReadAccountSnapshot = {
200
344
  portfolioValue: bigint;
201
345
  collateralValue: bigint;
@@ -513,12 +657,18 @@ export async function readBorrowerState(params: {
513
657
  return { user: params.user, scaledDebt, isBorrower, isLiquidatable };
514
658
  }
515
659
 
516
- // wraps: VarlaCore.getPortfolioValue
660
+ // wraps: VarlaCore.getAccountSummary
517
661
  export async function readPortfolioValue(params: {
518
- core: { read: { getPortfolioValue: (args: readonly [Address]) => Promise<bigint> } };
662
+ core: {
663
+ read: {
664
+ getAccountSummary: (
665
+ args: readonly [Address],
666
+ ) => Promise<readonly [bigint, bigint, bigint, bigint, bigint]>;
667
+ };
668
+ };
519
669
  user: Address;
520
670
  }): Promise<ReadPortfolioValue> {
521
- const portfolioValue = await params.core.read.getPortfolioValue([params.user]);
671
+ const [portfolioValue] = await params.core.read.getAccountSummary([params.user]);
522
672
  return { user: params.user, portfolioValue };
523
673
  }
524
674