@lifi/composer-sdk 0.2.0-staging.5 → 0.2.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 (74) hide show
  1. package/README.md +81 -0
  2. package/dist/client.cjs +38 -6
  3. package/dist/client.cjs.map +1 -1
  4. package/dist/client.d.cts +25 -1
  5. package/dist/client.d.ts +25 -1
  6. package/dist/client.js +41 -6
  7. package/dist/client.js.map +1 -1
  8. package/dist/errors.cjs +4 -1
  9. package/dist/errors.cjs.map +1 -1
  10. package/dist/errors.js +6 -1
  11. package/dist/errors.js.map +1 -1
  12. package/dist/generated/config.generated.cjs.map +1 -1
  13. package/dist/generated/config.generated.d.cts +44 -7
  14. package/dist/generated/config.generated.d.ts +44 -7
  15. package/dist/generated/materialisers.generated.cjs +2 -8
  16. package/dist/generated/materialisers.generated.cjs.map +1 -1
  17. package/dist/generated/materialisers.generated.d.cts +1 -1
  18. package/dist/generated/materialisers.generated.d.ts +1 -1
  19. package/dist/generated/materialisers.generated.js +1 -6
  20. package/dist/generated/materialisers.generated.js.map +1 -1
  21. package/dist/generated/operations.generated.cjs +1 -30
  22. package/dist/generated/operations.generated.cjs.map +1 -1
  23. package/dist/generated/operations.generated.d.cts +2 -94
  24. package/dist/generated/operations.generated.d.ts +2 -94
  25. package/dist/generated/operations.generated.js +1 -30
  26. package/dist/generated/operations.generated.js.map +1 -1
  27. package/dist/index.cjs +8 -0
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +4 -3
  30. package/dist/index.d.ts +4 -3
  31. package/dist/index.js +8 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/{materialisers.generated-BfWJ-g7t.d.ts → materialisers.generated-BMkBQgWZ.d.ts} +3 -8
  34. package/dist/{materialisers.generated-DaJRwiRe.d.cts → materialisers.generated-BWq5oePO.d.cts} +3 -8
  35. package/dist/responseSchemas.cjs +110 -0
  36. package/dist/responseSchemas.cjs.map +1 -0
  37. package/dist/responseSchemas.d.cts +36 -0
  38. package/dist/responseSchemas.d.ts +36 -0
  39. package/dist/responseSchemas.js +74 -0
  40. package/dist/responseSchemas.js.map +1 -0
  41. package/dist/run/simulate.cjs +41 -0
  42. package/dist/run/simulate.cjs.map +1 -0
  43. package/dist/run/simulate.d.cts +59 -0
  44. package/dist/run/simulate.d.ts +59 -0
  45. package/dist/run/simulate.js +17 -0
  46. package/dist/run/simulate.js.map +1 -0
  47. package/dist/sdk.cjs +6 -1
  48. package/dist/sdk.cjs.map +1 -1
  49. package/dist/sdk.d.cts +13 -1
  50. package/dist/sdk.d.ts +13 -1
  51. package/dist/sdk.js +6 -1
  52. package/dist/sdk.js.map +1 -1
  53. package/package.json +7 -5
  54. package/src/client.ts +86 -11
  55. package/src/errors.ts +8 -10
  56. package/src/examples/simulateCompiledSwap.ts +76 -0
  57. package/src/examples/simulateRawTransaction.ts +39 -0
  58. package/src/generated/config.generated.ts +179 -6
  59. package/src/generated/materialisers.generated.ts +0 -12
  60. package/src/generated/operations.generated.ts +0 -177
  61. package/src/index.ts +19 -0
  62. package/src/responseSchemas.ts +143 -0
  63. package/src/run/simulate.ts +78 -0
  64. package/src/sdk.ts +20 -1
  65. package/src/examples/staged/aaveBorrow.ts +0 -128
  66. package/src/examples/staged/aaveDebtRebalanceExactOut.ts +0 -301
  67. package/src/examples/staged/aaveHealthFactorExplode.ts +0 -70
  68. package/src/examples/staged/aaveToMorphoDebtMigration.ts +0 -200
  69. package/src/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts +0 -231
  70. package/src/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts +0 -307
  71. package/src/examples/staged/flashloanRepay.ts +0 -142
  72. package/src/examples/staged/morphoBlueBorrow.ts +0 -93
  73. package/src/examples/staged/morphoBlueRepay.ts +0 -132
  74. package/src/examples/staged/registry.ts +0 -80
@@ -20,7 +20,6 @@ import type { TypedGuard } from '../types.js';
20
20
  import type {
21
21
  AaveBorrowConfig,
22
22
  AaveClaimRewardsConfig,
23
- AaveGetHealthFactorConfig,
24
23
  AaveRepayConfig,
25
24
  AaveRepayWithATokensConfig,
26
25
  AaveSetEModeConfig,
@@ -47,16 +46,10 @@ import type {
47
46
  InvariantNeqConfig,
48
47
  InvariantNumericConfig,
49
48
  InvariantNumericInRangeConfig,
50
- LifiFlashloanRepayConfig,
51
49
  LifiSwapConfig,
52
50
  LifiZapConfig,
53
- MorphoBlueBorrowConfig,
54
- MorphoBlueGetPositionConfig,
55
- MorphoBlueRepayConfig,
56
51
  MorphoBlueSupplyCollateralConfig,
57
- MorphoBlueSupplyConfig,
58
52
  MorphoBlueWithdrawCollateralConfig,
59
- MorphoBlueWithdrawConfig,
60
53
  ParaswapBuyConfig,
61
54
  } from './config.generated.js';
62
55
 
@@ -507,13 +500,6 @@ export interface AaveSetEModeArgs {
507
500
  readonly guards?: readonly TypedGuard<never>[];
508
501
  }
509
502
 
510
- /** Read the current Aave v3 health factor (WAD-scaled, 1e18; type(uint256).max for users with no debt) by static-calling IPool.getUserAccountData and decomposing the returned tuple. Exposes the healthFactor element as a uint256 handle suitable for use with core.numericInvariant. */
511
- export interface AaveGetHealthFactorArgs {
512
- readonly bind: Record<string, never>;
513
- readonly config: AaveGetHealthFactorConfig;
514
- readonly guards?: readonly TypedGuard<'healthFactor'>[];
515
- }
516
-
517
503
  /** Deposit the collateral asset of a Morpho Blue market on behalf of the execution proxy. mode: 'exact' pulls the input handle's amount. mode: 'all' reads the proxy's current ERC20 balance of the market's collateral token and pulls that amount, ignoring the input handle's literal value — note this pulls the proxy's total balance (including unrelated upstream residuals), not just the input port's delivery. Credits Position.collateral on the Morpho singleton; emits no return value and exposes no resource output (the collateral is non-transferable accounting state). */
518
504
  export interface MorphoBlueSupplyCollateralArgs {
519
505
  readonly bind: {
@@ -530,64 +516,6 @@ export interface MorphoBlueWithdrawCollateralArgs {
530
516
  readonly guards?: readonly TypedGuard<'collateralWithdrawn'>[];
531
517
  }
532
518
 
533
- /** Borrow the loan asset from a Morpho Blue market against existing collateral. Exposes the borrowed amount as a Resource and the (assetsBorrowed, sharesBorrowed) tuple as handles. */
534
- export interface MorphoBlueBorrowArgs {
535
- readonly bind: Record<string, never>;
536
- readonly config: MorphoBlueBorrowConfig;
537
- readonly guards?: readonly TypedGuard<'assetsBorrowed' | 'sharesBorrowed'>[];
538
- }
539
-
540
- /** Repay a Morpho Blue debt position with the supplied asset. mode: 'exact' repays the input handle amount; mode: 'max' reads Position.borrowShares via a precursor static call and repays in shares mode to retire the debt to the wei. Returns any unspent residual as a Resource. */
541
- export interface MorphoBlueRepayArgs {
542
- readonly bind: {
543
- readonly assetIn: Bindable<'resource'>;
544
- readonly onBehalfOf: Bindable<'address'>;
545
- };
546
- readonly config: MorphoBlueRepayConfig;
547
- readonly guards?: readonly TypedGuard<
548
- 'actualRepaid' | 'sharesRepaid' | 'residual'
549
- >[];
550
- }
551
-
552
- /** Supply the loan asset of a Morpho Blue market and receive supplyShares accounting credit. Returns the (assetsSupplied, sharesSupplied) tuple as handles and any unspent residual as a Resource. */
553
- export interface MorphoBlueSupplyArgs {
554
- readonly bind: {
555
- readonly assetIn: Bindable<'resource'>;
556
- };
557
- readonly config: MorphoBlueSupplyConfig;
558
- readonly guards?: readonly TypedGuard<
559
- 'assetsSupplied' | 'sharesSupplied' | 'residual'
560
- >[];
561
- }
562
-
563
- /** Withdraw the loan asset from a Morpho Blue supply position. mode: 'exact' withdraws the configured amount; mode: 'max' reads Position.supplyShares via a precursor static call and burns all shares to retire the supply to the wei. Exposes the withdrawn amount as a Resource. */
564
- export interface MorphoBlueWithdrawArgs {
565
- readonly bind: Record<string, never>;
566
- readonly config: MorphoBlueWithdrawConfig;
567
- readonly guards?: readonly TypedGuard<
568
- 'assetsWithdrawn' | 'sharesWithdrawn'
569
- >[];
570
- }
571
-
572
- /** Read a Morpho Blue Position struct via a static call on the singleton. Exposes (supplyShares, borrowShares, collateral) as handles whose kinds match the on-chain field types (uint256, uint128, uint128). */
573
- export interface MorphoBlueGetPositionArgs {
574
- readonly bind: Record<string, never>;
575
- readonly config: MorphoBlueGetPositionConfig;
576
- readonly guards?: readonly TypedGuard<
577
- 'supplyShares' | 'borrowShares' | 'collateral'
578
- >[];
579
- }
580
-
581
- /** Transfer principal + fee back to the LiFi flashloan adapter for one leg */
582
- export interface LifiFlashloanRepayArgs {
583
- readonly bind: {
584
- readonly funds: Bindable<'resource'>;
585
- readonly feeBuffer?: Bindable<'resource'>;
586
- };
587
- readonly config: LifiFlashloanRepayConfig;
588
- readonly guards?: readonly TypedGuard<never>[];
589
- }
590
-
591
519
  /** Route tokens into a DeFi protocol position via the edge catalog */
592
520
  export interface LifiZapArgs {
593
521
  readonly bind: {
@@ -692,45 +620,11 @@ export interface AaveClaimRewardsOutputs {
692
620
  readonly claimedAmount: OutputHandle<'uint256'>;
693
621
  }
694
622
 
695
- export interface AaveGetHealthFactorOutputs {
696
- readonly healthFactor: OutputHandle<'uint256'>;
697
- }
698
-
699
623
  export interface MorphoBlueWithdrawCollateralOutputs {
700
624
  readonly withdrawn: OutputHandle<'resource'>;
701
625
  readonly collateralWithdrawn: OutputHandle<'uint256'>;
702
626
  }
703
627
 
704
- export interface MorphoBlueBorrowOutputs {
705
- readonly borrowed: OutputHandle<'resource'>;
706
- readonly assetsBorrowed: OutputHandle<'uint256'>;
707
- readonly sharesBorrowed: OutputHandle<'uint256'>;
708
- }
709
-
710
- export interface MorphoBlueRepayOutputs {
711
- readonly actualRepaid: OutputHandle<'uint256'>;
712
- readonly sharesRepaid: OutputHandle<'uint256'>;
713
- readonly residual: OutputHandle<'resource'>;
714
- }
715
-
716
- export interface MorphoBlueSupplyOutputs {
717
- readonly assetsSupplied: OutputHandle<'uint256'>;
718
- readonly sharesSupplied: OutputHandle<'uint256'>;
719
- readonly residual: OutputHandle<'resource'>;
720
- }
721
-
722
- export interface MorphoBlueWithdrawOutputs {
723
- readonly withdrawn: OutputHandle<'resource'>;
724
- readonly assetsWithdrawn: OutputHandle<'uint256'>;
725
- readonly sharesWithdrawn: OutputHandle<'uint256'>;
726
- }
727
-
728
- export interface MorphoBlueGetPositionOutputs {
729
- readonly supplyShares: OutputHandle<'uint256'>;
730
- readonly borrowShares: OutputHandle<'uint128'>;
731
- readonly collateral: OutputHandle<'uint128'>;
732
- }
733
-
734
628
  export interface LifiZapOutputs {
735
629
  readonly amountOut: OutputHandle<'resource'>;
736
630
  }
@@ -845,11 +739,6 @@ export type GeneratedOps = {
845
739
  readonly lifi: {
846
740
  /** Swap tokens via the LI.FI aggregator API — original id: "lifi.swap" */
847
741
  readonly swap: (nodeId: string, args: LifiSwapArgs) => LifiSwapOutputs;
848
- /** Transfer principal + fee back to the LiFi flashloan adapter for one leg — original id: "lifi.flashloanRepay" */
849
- readonly flashloanRepay: (
850
- nodeId: string,
851
- args: LifiFlashloanRepayArgs,
852
- ) => void;
853
742
  /** Route tokens into a DeFi protocol position via the edge catalog — original id: "lifi.zap" */
854
743
  readonly zap: (nodeId: string, args: LifiZapArgs) => LifiZapOutputs;
855
744
  };
@@ -934,11 +823,6 @@ export type GeneratedOps = {
934
823
  ) => AaveClaimRewardsOutputs;
935
824
  /** Switch the executionAddress's Aave v3 eMode category. 0 disables eMode. The pool reverts if the switch would make the position unhealthy or if any open borrow is for an asset outside the target category. — original id: "aave.setEMode" */
936
825
  readonly setEMode: (nodeId: string, args: AaveSetEModeArgs) => void;
937
- /** Read the current Aave v3 health factor (WAD-scaled, 1e18; type(uint256).max for users with no debt) by static-calling IPool.getUserAccountData and decomposing the returned tuple. Exposes the healthFactor element as a uint256 handle suitable for use with core.numericInvariant. — original id: "aave.getHealthFactor" */
938
- readonly getHealthFactor: (
939
- nodeId: string,
940
- args: AaveGetHealthFactorArgs,
941
- ) => AaveGetHealthFactorOutputs;
942
826
  };
943
827
  readonly morphoBlue: {
944
828
  /** Deposit the collateral asset of a Morpho Blue market on behalf of the execution proxy. mode: 'exact' pulls the input handle's amount. mode: 'all' reads the proxy's current ERC20 balance of the market's collateral token and pulls that amount, ignoring the input handle's literal value — note this pulls the proxy's total balance (including unrelated upstream residuals), not just the input port's delivery. Credits Position.collateral on the Morpho singleton; emits no return value and exposes no resource output (the collateral is non-transferable accounting state). — original id: "morphoBlue.supplyCollateral" */
@@ -951,31 +835,6 @@ export type GeneratedOps = {
951
835
  nodeId: string,
952
836
  args: MorphoBlueWithdrawCollateralArgs,
953
837
  ) => MorphoBlueWithdrawCollateralOutputs;
954
- /** Borrow the loan asset from a Morpho Blue market against existing collateral. Exposes the borrowed amount as a Resource and the (assetsBorrowed, sharesBorrowed) tuple as handles. — original id: "morphoBlue.borrow" */
955
- readonly borrow: (
956
- nodeId: string,
957
- args: MorphoBlueBorrowArgs,
958
- ) => MorphoBlueBorrowOutputs;
959
- /** Repay a Morpho Blue debt position with the supplied asset. mode: 'exact' repays the input handle amount; mode: 'max' reads Position.borrowShares via a precursor static call and repays in shares mode to retire the debt to the wei. Returns any unspent residual as a Resource. — original id: "morphoBlue.repay" */
960
- readonly repay: (
961
- nodeId: string,
962
- args: MorphoBlueRepayArgs,
963
- ) => MorphoBlueRepayOutputs;
964
- /** Supply the loan asset of a Morpho Blue market and receive supplyShares accounting credit. Returns the (assetsSupplied, sharesSupplied) tuple as handles and any unspent residual as a Resource. — original id: "morphoBlue.supply" */
965
- readonly supply: (
966
- nodeId: string,
967
- args: MorphoBlueSupplyArgs,
968
- ) => MorphoBlueSupplyOutputs;
969
- /** Withdraw the loan asset from a Morpho Blue supply position. mode: 'exact' withdraws the configured amount; mode: 'max' reads Position.supplyShares via a precursor static call and burns all shares to retire the supply to the wei. Exposes the withdrawn amount as a Resource. — original id: "morphoBlue.withdraw" */
970
- readonly withdraw: (
971
- nodeId: string,
972
- args: MorphoBlueWithdrawArgs,
973
- ) => MorphoBlueWithdrawOutputs;
974
- /** Read a Morpho Blue Position struct via a static call on the singleton. Exposes (supplyShares, borrowShares, collateral) as handles whose kinds match the on-chain field types (uint256, uint128, uint128). — original id: "morphoBlue.getPosition" */
975
- readonly getPosition: (
976
- nodeId: string,
977
- args: MorphoBlueGetPositionArgs,
978
- ) => MorphoBlueGetPositionOutputs;
979
838
  };
980
839
  readonly paraswap: {
981
840
  /** Exact-output (BUY) swap via the Paraswap/Velora Market API: produce exactly `exactAmountOut` of the destination token, pulling only the source needed and exposing the genuinely-unspent source as `unspentIn`. — original id: "paraswap.buy" */
@@ -1097,8 +956,6 @@ export const bindGeneratedOps = (
1097
956
  amountOut: 'resource',
1098
957
  unspentIn: 'resource',
1099
958
  } as const),
1100
- flashloanRepay: (nodeId: string, args: LifiFlashloanRepayArgs) =>
1101
- builder.call(nodeId, 'lifi.flashloanRepay', args, {}),
1102
959
  zap: (nodeId: string, args: LifiZapArgs) =>
1103
960
  builder.call(nodeId, 'lifi.zap', args, {
1104
961
  amountOut: 'resource',
@@ -1201,10 +1058,6 @@ export const bindGeneratedOps = (
1201
1058
  } as const),
1202
1059
  setEMode: (nodeId: string, args: AaveSetEModeArgs) =>
1203
1060
  builder.call(nodeId, 'aave.setEMode', args, {}),
1204
- getHealthFactor: (nodeId: string, args: AaveGetHealthFactorArgs) =>
1205
- builder.call(nodeId, 'aave.getHealthFactor', args, {
1206
- healthFactor: 'uint256',
1207
- } as const),
1208
1061
  },
1209
1062
  morphoBlue: {
1210
1063
  supplyCollateral: (nodeId: string, args: MorphoBlueSupplyCollateralArgs) =>
@@ -1217,36 +1070,6 @@ export const bindGeneratedOps = (
1217
1070
  withdrawn: 'resource',
1218
1071
  collateralWithdrawn: 'uint256',
1219
1072
  } as const),
1220
- borrow: (nodeId: string, args: MorphoBlueBorrowArgs) =>
1221
- builder.call(nodeId, 'morphoBlue.borrow', args, {
1222
- borrowed: 'resource',
1223
- assetsBorrowed: 'uint256',
1224
- sharesBorrowed: 'uint256',
1225
- } as const),
1226
- repay: (nodeId: string, args: MorphoBlueRepayArgs) =>
1227
- builder.call(nodeId, 'morphoBlue.repay', args, {
1228
- actualRepaid: 'uint256',
1229
- sharesRepaid: 'uint256',
1230
- residual: 'resource',
1231
- } as const),
1232
- supply: (nodeId: string, args: MorphoBlueSupplyArgs) =>
1233
- builder.call(nodeId, 'morphoBlue.supply', args, {
1234
- assetsSupplied: 'uint256',
1235
- sharesSupplied: 'uint256',
1236
- residual: 'resource',
1237
- } as const),
1238
- withdraw: (nodeId: string, args: MorphoBlueWithdrawArgs) =>
1239
- builder.call(nodeId, 'morphoBlue.withdraw', args, {
1240
- withdrawn: 'resource',
1241
- assetsWithdrawn: 'uint256',
1242
- sharesWithdrawn: 'uint256',
1243
- } as const),
1244
- getPosition: (nodeId: string, args: MorphoBlueGetPositionArgs) =>
1245
- builder.call(nodeId, 'morphoBlue.getPosition', args, {
1246
- supplyShares: 'uint256',
1247
- borrowShares: 'uint128',
1248
- collateral: 'uint128',
1249
- } as const),
1250
1073
  },
1251
1074
  paraswap: {
1252
1075
  buy: (nodeId: string, args: ParaswapBuyArgs) =>
package/src/index.ts CHANGED
@@ -51,6 +51,22 @@ export type {
51
51
  SimulationRevert,
52
52
  SolType,
53
53
  SweepTo,
54
+ SimulateRequest,
55
+ SimulateResult,
56
+ SimulateOkResult,
57
+ SimulateRevertResult,
58
+ SimulateRevertDecodeResult,
59
+ SimulateSetupErrorResult,
60
+ SimulateBalanceEntry,
61
+ TrackedBalance,
62
+ SlotFinderRequirement,
63
+ Erc20BalanceRequirement,
64
+ NativeBalanceRequirement,
65
+ Erc20AllowanceRequirement,
66
+ } from '@lifi/compose-spec';
67
+ export {
68
+ SIMULATE_MAX_TRACKED_BALANCES,
69
+ SIMULATE_MAX_REQUIREMENTS,
54
70
  } from '@lifi/compose-spec';
55
71
  export { isComposeError } from './errors.js';
56
72
 
@@ -70,6 +86,9 @@ export type { TypedFlow, FlowOptions } from './authoring/FlowBuilderCore.js';
70
86
  export type { ComposeRunInput } from './run/inputs.js';
71
87
  export { materialiser } from './run/inputs.js';
72
88
 
89
+ export { buildSimulateRequest } from './run/simulate.js';
90
+ export type { BuildSimulateRequestInput } from './run/simulate.js';
91
+
73
92
  export { createComposeSdk } from './sdk.js';
74
93
  export type { ComposeSdk, ComposeSdkOptions, FlowBuilder } from './sdk.js';
75
94
 
@@ -0,0 +1,143 @@
1
+ import type { SimulateResult, SimulationRevert } from '@lifi/compose-spec';
2
+ import z from 'zod';
3
+
4
+ /**
5
+ * Zod-backed parsers for Compose API response bodies.
6
+ *
7
+ * Server responses are untrusted input crossing into the SDK, so the shapes the
8
+ * client reads back are validated here rather than asserted with `as` casts. The
9
+ * schemas are module-private (the package builds with `--isolatedDeclarations`,
10
+ * which forbids exporting un-annotated complex values); the public surface is
11
+ * the typed `parse*` helpers, whose explicit return types double as the contract.
12
+ *
13
+ * The simulate union is the one response the SDK fully owns and parses itself
14
+ * (it is un-enveloped, unlike `/compose`), so it gets a complete schema kept in
15
+ * lockstep with the canonical `SimulateResult` in `@lifi/compose-spec` by the
16
+ * compile-time assertion below — the same anchoring `@lifi/api-schemas` uses for
17
+ * its server-side simulate schema, so the two cannot silently drift.
18
+ */
19
+
20
+ const balanceEntrySchema = z.object({
21
+ token: z.string(),
22
+ owner: z.string(),
23
+ amount: z.string(),
24
+ });
25
+
26
+ const simulateOkSchema = z.object({
27
+ status: z.literal('ok'),
28
+ block: z.number(),
29
+ timestamp: z.number(),
30
+ balancesBefore: z.array(balanceEntrySchema),
31
+ balancesAfter: z.array(balanceEntrySchema),
32
+ deltas: z.array(balanceEntrySchema),
33
+ gasUsed: z.string(),
34
+ });
35
+
36
+ const simulateRevertDecodeResultSchema = z.object({
37
+ errorCandidates: z
38
+ .array(
39
+ z.object({
40
+ decodedErrorSignature: z.string(),
41
+ decodedParams: z.array(z.string()),
42
+ }),
43
+ )
44
+ .optional(),
45
+ error: z.string().optional(),
46
+ });
47
+
48
+ const simulateRevertSchema = z.object({
49
+ status: z.literal('revert'),
50
+ block: z.number(),
51
+ timestamp: z.number(),
52
+ revertReason: z.string().optional(),
53
+ code: z.number().optional(),
54
+ rawErrorBytes: z.string().optional(),
55
+ decodeResult: simulateRevertDecodeResultSchema.optional(),
56
+ });
57
+
58
+ const simulateSetupErrorSchema = z.object({
59
+ status: z.literal('error'),
60
+ message: z.string(),
61
+ });
62
+
63
+ const simulateResultSchema = z.discriminatedUnion('status', [
64
+ simulateOkSchema,
65
+ simulateRevertSchema,
66
+ simulateSetupErrorSchema,
67
+ ]);
68
+
69
+ // The type the schema actually parses out. Kept private (referencing it from an
70
+ // exported declaration would break `--isolatedDeclarations`) and pinned to the
71
+ // canonical `SimulateResult` inside `parseSimulateResult` below.
72
+ type SimulateResultWire = z.infer<typeof simulateResultSchema>;
73
+
74
+ /**
75
+ * Parses a `POST /simulate` body into a {@link SimulateResult}. Returns `null`
76
+ * when the body does not match the `ok`/`revert`/`error` union, letting the
77
+ * caller raise a transport-level error.
78
+ */
79
+ export const parseSimulateResult = (body: unknown): SimulateResult | null => {
80
+ const parsed = simulateResultSchema.safeParse(body);
81
+ if (!parsed.success) return null;
82
+ // Pin the schema to the canonical contract in both directions, so it cannot
83
+ // silently drift (mirroring the conformance assertion `@lifi/api-schemas` runs
84
+ // on its server-side simulate schema). Assigning the inferred wire value to a
85
+ // `SimulateResult` checks wire→spec; `satisfies SimulateResultWire` checks
86
+ // spec→wire. A missing or mistyped field on either side fails to compile.
87
+ const result: SimulateResult = parsed.data;
88
+ return result satisfies SimulateResultWire;
89
+ };
90
+
91
+ /**
92
+ * The validated members of an HTTP 206 partial compile envelope. `data` is
93
+ * verified to be an object but kept untyped — compose-spec owns its full shape
94
+ * (`ComposeCompilePartialData`) as a hand-authored type, so the caller narrows
95
+ * it. `error` is fully validated.
96
+ */
97
+ export interface CompilePartialEnvelope {
98
+ readonly data: Record<string, unknown>;
99
+ readonly error: { readonly kind: string; readonly message: string };
100
+ }
101
+
102
+ const compilePartialEnvelopeSchema = z.object({
103
+ data: z.record(z.string(), z.unknown()),
104
+ error: z.object({ kind: z.string(), message: z.string() }),
105
+ });
106
+
107
+ /** Parses an HTTP 206 partial compile envelope; `null` when it does not match. */
108
+ export const parseCompilePartialEnvelope = (
109
+ body: unknown,
110
+ ): CompilePartialEnvelope | null => {
111
+ const parsed = compilePartialEnvelopeSchema.safeParse(body);
112
+ return parsed.success ? parsed.data : null;
113
+ };
114
+
115
+ /** Error envelope returned on non-2xx responses. */
116
+ export interface ServerErrorBody {
117
+ readonly error?: {
118
+ readonly kind?: string;
119
+ readonly message?: string;
120
+ readonly path?: string;
121
+ readonly details?: SimulationRevert;
122
+ };
123
+ }
124
+
125
+ // `details` is passed through (`z.custom`) rather than parsed: the SDK does not
126
+ // own the `SimulationRevert` schema and forwards the field verbatim onto
127
+ // `ComposeError`.
128
+ const serverErrorBodySchema = z.object({
129
+ error: z
130
+ .object({
131
+ kind: z.string().optional(),
132
+ message: z.string().optional(),
133
+ path: z.string().optional(),
134
+ details: z.custom<SimulationRevert>().optional(),
135
+ })
136
+ .optional(),
137
+ });
138
+
139
+ /** Validates an already-parsed error envelope; `null` when it does not match. */
140
+ export const parseServerErrorBody = (json: unknown): ServerErrorBody | null => {
141
+ const parsed = serverErrorBodySchema.safeParse(json);
142
+ return parsed.success ? parsed.data : null;
143
+ };
@@ -0,0 +1,78 @@
1
+ import type {
2
+ ComposeCompileResult,
3
+ SimulateRequest,
4
+ SlotFinderRequirement,
5
+ TrackedBalance,
6
+ } from '@lifi/compose-spec';
7
+
8
+ /**
9
+ * Input for {@link buildSimulateRequest}: a compile result plus the fields a
10
+ * simulation needs that a compiled flow does not carry.
11
+ */
12
+ export interface BuildSimulateRequestInput {
13
+ /**
14
+ * A result from `builder.compile(...)` / `client.compile(...)`. Both
15
+ * `success` and `partial` results carry a `transactionRequest`, so either is
16
+ * valid input — the helper does not discriminate on `status`.
17
+ */
18
+ readonly result: ComposeCompileResult;
19
+ /** EVM chain id. A compile result does not carry the target chain. */
20
+ readonly chainId: number;
21
+ /**
22
+ * The transaction sender (`from`). A compile result does not carry the
23
+ * signer, so it must be supplied here.
24
+ */
25
+ readonly signer: string;
26
+ /**
27
+ * Balances to watch across the simulation. These cannot be inferred from a
28
+ * flow and must be supplied by the caller.
29
+ */
30
+ readonly trackedBalances: readonly TrackedBalance[];
31
+ /** Optional funding instructions applied before simulation. */
32
+ readonly requirements?: readonly SlotFinderRequirement[];
33
+ /** Optional block to pin the simulation to. Omitted ⇒ chain head. */
34
+ readonly block?: number;
35
+ /**
36
+ * Optional native value override. Defaults to the compiled
37
+ * `transactionRequest.value` when omitted.
38
+ */
39
+ readonly value?: bigint | string;
40
+ }
41
+
42
+ /**
43
+ * Assembles a {@link SimulateRequest} from a compile result and the
44
+ * caller-supplied chain, signer, and tracked balances. Pure: performs no
45
+ * network I/O, does not mutate its input, and returns a fresh object.
46
+ *
47
+ * Bridges the common case — simulate the transaction you just compiled —
48
+ * without coupling raw-transaction simulation to the flow builder. The
49
+ * `value` defaults to the compiled transaction's value unless overridden.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const compiled = await builder.compile({ inputs: { ... }, signer: '0x1111...' });
54
+ * const req = buildSimulateRequest({
55
+ * result: compiled,
56
+ * chainId: 1,
57
+ * signer: '0x1111111111111111111111111111111111111111',
58
+ * trackedBalances: [{ token: '0xA0b8...', owner: '0x1111...' }],
59
+ * });
60
+ * const sim = await sdk.client.simulate(req);
61
+ * ```
62
+ */
63
+ export const buildSimulateRequest = (
64
+ input: BuildSimulateRequestInput,
65
+ ): SimulateRequest => {
66
+ const { result, chainId, signer, trackedBalances, requirements, block } =
67
+ input;
68
+ return {
69
+ chainId,
70
+ from: signer,
71
+ to: result.transactionRequest.to,
72
+ data: result.transactionRequest.data,
73
+ value: input.value ?? result.transactionRequest.value,
74
+ block,
75
+ requirements,
76
+ trackedBalances,
77
+ };
78
+ };
package/src/sdk.ts CHANGED
@@ -2,6 +2,8 @@ import type {
2
2
  ComposeCompileRequest,
3
3
  ComposeCompileResult,
4
4
  InputSpec,
5
+ SimulateRequest,
6
+ SimulateResult,
5
7
  } from '@lifi/compose-spec';
6
8
 
7
9
  import {
@@ -117,6 +119,18 @@ export interface ComposeSdk {
117
119
  flow: TypedFlow<T>,
118
120
  run: ComposeRunInput<T>,
119
121
  ) => ComposeCompileRequest;
122
+ /**
123
+ * Simulates a raw, pre-encoded transaction. Thin pass-through to
124
+ * {@link ComposeClient.simulate}; see it for the `ok` / `revert` / `error`
125
+ * result discrimination and error behaviour. Pair with `buildSimulateRequest`
126
+ * to assemble the request from a compile result.
127
+ *
128
+ * @param request - The raw transaction plus funding `requirements` and
129
+ * `trackedBalances`.
130
+ * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).
131
+ * @throws {@link ComposeError} on network, validation, or server errors.
132
+ */
133
+ readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;
120
134
  }
121
135
 
122
136
  /**
@@ -170,5 +184,10 @@ export const createComposeSdk = (options: ComposeSdkOptions): ComposeSdk => {
170
184
  return Object.assign(builder, { compile });
171
185
  };
172
186
 
173
- return { client: composeClient, flow, request };
187
+ return {
188
+ client: composeClient,
189
+ flow,
190
+ request,
191
+ simulate: composeClient.simulate,
192
+ };
174
193
  };
@@ -1,128 +0,0 @@
1
- import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
2
-
3
- import {
4
- createComposeSdk,
5
- guards,
6
- materialisers,
7
- resources,
8
- } from '../../index.js';
9
- import type { Address } from '../../types.js';
10
- import { BASE_URL } from '../config.js';
11
-
12
- // Aave v3 contracts on Ethereum mainnet.
13
- const AAVE_V3_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2';
14
- const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
15
- // Aave v3 aEthUSDC receipt token and variable-debt USDC token.
16
- const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
17
- const VDEBT_USDC = '0x72E95b8931767C79bA4EeE721354d6E99a61D004';
18
-
19
- // 1.05 WAD (1e18) — minimum acceptable Aave health factor after the borrow.
20
- // The on-chain VM reverts the run if the post-borrow HF drops below this.
21
- const MIN_HEALTH_FACTOR_WAD = '1050000000000000000';
22
-
23
- export interface AaveBorrowInput {
24
- readonly owner: Address;
25
- readonly recipient: Address;
26
- /** USDC supplied as collateral so the proxy can borrow (6 dp). */
27
- readonly collateralAmount: `${bigint}`;
28
- /** USDC borrowed against the collateral and sent to the recipient (6 dp). */
29
- readonly borrowAmount: `${bigint}`;
30
- }
31
-
32
- /**
33
- * Supply USDC to Aave v3, borrow USDC against it, and transfer the borrowed
34
- * funds to a recipient — with an on-chain health-factor guard that reverts
35
- * if the post-borrow position is too thin.
36
- *
37
- * The in-flow supply makes the example self-contained: the borrow is backed
38
- * by collateral created in the same transaction, so it simulates cleanly
39
- * for a fresh proxy. Keep `borrowAmount` comfortably below the collateral's
40
- * borrowing power or the health-factor guard reverts the run.
41
- *
42
- * Demonstrates:
43
- * - `lifi.zap` to supply USDC into Aave (USDC → aEthUSDC routing edge)
44
- * - `aave.borrow` with no resource input — the borrowed asset materialises
45
- * as the `borrowed` resource output.
46
- * - Chaining the borrow output into a `core.transfer` to forward funds.
47
- * - `aave.getHealthFactor` + `core.numericInvariant` guard asserts that
48
- * `healthFactor >= MIN_HEALTH_FACTOR_WAD` after the borrow; the run reverts
49
- * on-chain if the threshold is violated.
50
- *
51
- * The supplied aEthUSDC stays on the proxy: it backs the open debt, and
52
- * Aave rejects aToken transfers that would break the position's health
53
- * factor, so it cannot be swept while the borrow is outstanding.
54
- */
55
- export const buildAaveBorrow = ({
56
- owner,
57
- recipient,
58
- collateralAmount,
59
- borrowAmount,
60
- }: AaveBorrowInput): {
61
- flow: Flow;
62
- request: ComposeCompileRequest;
63
- } => {
64
- const sdk = createComposeSdk({ baseUrl: BASE_URL });
65
-
66
- const builder = sdk.flow(1, {
67
- name: 'aave-borrow-usdc',
68
- inputs: {
69
- collateralIn: resources.erc20(USDC, 1),
70
- recipient: 'address',
71
- },
72
- });
73
-
74
- // Supply USDC as collateral (USDC → aEthUSDC) so the proxy can borrow.
75
- builder.lifi.zap('supply', {
76
- bind: { amountIn: builder.inputs.collateralIn },
77
- config: { resourceOut: resources.erc20(A_ETH_USDC, 1) },
78
- });
79
-
80
- const borrowOut = builder.aave.borrow('borrow', {
81
- bind: {},
82
- config: {
83
- pool: AAVE_V3_POOL,
84
- asset: USDC,
85
- variableDebtToken: VDEBT_USDC,
86
- amount: borrowAmount,
87
- },
88
- });
89
-
90
- // Read the post-borrow Aave health factor and assert it stays above the
91
- // minimum threshold. The numericInvariant guard lowers to an on-chain
92
- // `AssertRawInvariant` IR1 instruction; the run reverts if HF < threshold.
93
- builder.aave.getHealthFactor('hf', {
94
- bind: {},
95
- config: { pool: AAVE_V3_POOL },
96
- guards: [
97
- guards.coreNumericInvariant({
98
- port: 'healthFactor',
99
- op: 'gte',
100
- threshold: MIN_HEALTH_FACTOR_WAD,
101
- }),
102
- ],
103
- });
104
-
105
- // Forward the freshly borrowed USDC to the recipient.
106
- builder.core.transfer('send-borrowed', {
107
- bind: {
108
- amount: borrowOut.borrowed,
109
- recipient: builder.inputs.recipient,
110
- },
111
- config: {},
112
- });
113
-
114
- const flow = builder.build();
115
-
116
- // No sweepTo: the aEthUSDC collateral must stay on the proxy while the
117
- // debt is open — Aave blocks aToken transfers that would leave the
118
- // position undercollateralized.
119
- const request = sdk.request(flow, {
120
- signer: owner,
121
- inputs: {
122
- collateralIn: materialisers.directDeposit({ amount: collateralAmount }),
123
- recipient,
124
- },
125
- });
126
-
127
- return { flow, request };
128
- };