@lifi/composer-sdk 0.4.0 → 0.4.1

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 (56) hide show
  1. package/README.md +95 -5
  2. package/dist/authoring/raw.cjs +3 -1
  3. package/dist/authoring/raw.cjs.map +1 -1
  4. package/dist/authoring/raw.js +3 -1
  5. package/dist/authoring/raw.js.map +1 -1
  6. package/dist/client.cjs +26 -24
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +31 -1
  9. package/dist/client.d.ts +31 -1
  10. package/dist/client.js +26 -24
  11. package/dist/client.js.map +1 -1
  12. package/dist/discovery.cjs.map +1 -1
  13. package/dist/discovery.d.cts +13 -0
  14. package/dist/discovery.d.ts +13 -0
  15. package/dist/generated/config.generated.cjs.map +1 -1
  16. package/dist/generated/config.generated.d.cts +15 -3
  17. package/dist/generated/config.generated.d.ts +15 -3
  18. package/dist/generated/operations.generated.cjs +3 -0
  19. package/dist/generated/operations.generated.cjs.map +1 -1
  20. package/dist/generated/operations.generated.d.cts +17 -2
  21. package/dist/generated/operations.generated.d.ts +17 -2
  22. package/dist/generated/operations.generated.js +3 -0
  23. package/dist/generated/operations.generated.js.map +1 -1
  24. package/dist/index.cjs +5 -1
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +22 -3
  27. package/dist/index.d.ts +22 -3
  28. package/dist/index.js +4 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/routeAmount-DdbJYJyM.d.cts +55 -0
  31. package/dist/routeAmount-DdbJYJyM.d.ts +55 -0
  32. package/dist/routeAmount.cjs +38 -0
  33. package/dist/routeAmount.cjs.map +1 -0
  34. package/dist/routeAmount.d.cts +2 -0
  35. package/dist/routeAmount.d.ts +2 -0
  36. package/dist/routeAmount.js +13 -0
  37. package/dist/routeAmount.js.map +1 -0
  38. package/dist/sdk.cjs +1 -0
  39. package/dist/sdk.cjs.map +1 -1
  40. package/dist/sdk.d.cts +14 -1
  41. package/dist/sdk.d.ts +14 -1
  42. package/dist/sdk.js +1 -0
  43. package/dist/sdk.js.map +1 -1
  44. package/package.json +5 -5
  45. package/src/authoring/raw.ts +3 -2
  46. package/src/client.ts +75 -23
  47. package/src/discovery.ts +13 -0
  48. package/src/examples/lifiZap.ts +4 -0
  49. package/src/examples/lifiZapAsync.ts +88 -0
  50. package/src/examples/routeTokenPair.ts +58 -0
  51. package/src/examples/run.ts +30 -5
  52. package/src/generated/config.generated.ts +21 -3
  53. package/src/generated/operations.generated.ts +30 -8
  54. package/src/index.ts +23 -0
  55. package/src/routeAmount.ts +61 -0
  56. package/src/sdk.ts +17 -0
@@ -0,0 +1,88 @@
1
+ import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
2
+
3
+ import { createComposeSdk, materialisers, resources } from '../index.js';
4
+ import type { Address } from '../types.js';
5
+
6
+ import { API_KEY, BASE_URL } from './config.js';
7
+
8
+ const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
9
+ // Aave aEthUSDC receipt token on Ethereum mainnet
10
+ const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
11
+
12
+ export interface LifiZapAsyncInput {
13
+ readonly owner: Address;
14
+ readonly recipient: Address;
15
+ }
16
+
17
+ /**
18
+ * Zap USDC into a DeFi position whose output settles asynchronously via LI.FI.
19
+ *
20
+ * Demonstrates:
21
+ * - `lifi.zapAsync`, the future-availability sibling of `lifi.zap`
22
+ * - Binding the required `recipient` input (the async op has no optional
23
+ * recipient — the decl is non-optional, so `bind.recipient` is mandatory)
24
+ * - Omitting `sweepTo`: `availability: 'future'` governs custody, so the
25
+ * output is delivered externally and never enters the execution VM — a
26
+ * sweep would find nothing to move (custody, not amount reporting)
27
+ * - Amount reporting is unaffected by availability: when the async lowering
28
+ * binds a terminal handle the output still reports `simulated.amountOut`,
29
+ * the protocol's in-transaction expected value (an estimate; the delivered
30
+ * amount arrives after settlement). `simulated.amountOutMin` appears only
31
+ * when a minimum source exists — for a future output that means a floor the
32
+ * edge lowering itself reported, never a user guard
33
+ * - No output guards: the reported value is the initiating-transaction
34
+ * estimate, not the asynchronously delivered amount a guard could enforce,
35
+ * so `amountOut` is excluded from the generated `TypedGuard` union
36
+ * (`guards` typechecks as `TypedGuard<never>[]`) and the compiler rejects
37
+ * any guard on it
38
+ */
39
+ export const buildLifiZapAsyncExample = ({
40
+ owner,
41
+ recipient,
42
+ }: LifiZapAsyncInput): {
43
+ flow: Flow;
44
+ request: ComposeCompileRequest;
45
+ } => {
46
+ const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
47
+
48
+ // Declare the flow with a single USDC input and the recipient address input.
49
+ const builder = sdk.flow(1, {
50
+ name: 'zap-async-usdc-to-aave',
51
+ inputs: {
52
+ amountIn: resources.erc20(USDC, 1),
53
+ recipient: 'address',
54
+ },
55
+ });
56
+
57
+ // Zap USDC into an async Aave position via LI.FI.
58
+ // The future output is delivered to `recipient` once the initiating
59
+ // transaction settles; the recipient binding is required by the op.
60
+ builder.lifi.zapAsync('zap', {
61
+ bind: {
62
+ amountIn: builder.inputs.amountIn,
63
+ recipient: builder.inputs.recipient,
64
+ },
65
+ config: {
66
+ resourceOut: resources.erc20(A_ETH_USDC, 1),
67
+ },
68
+ });
69
+
70
+ const flow = builder.build();
71
+
72
+ // Build the compile request.
73
+ // directDeposit transfers a fixed amount of USDC into the VM; the recipient
74
+ // handle passes the delivery address as a plain string.
75
+ // No `sweepTo`: the async output is delivered externally to the recipient,
76
+ // never landing in the VM for a sweep to reach.
77
+ // NOTE: no production edge sets `availability: 'future'` yet, so this request
78
+ // resolves to a typed no_route today; it documents the intended shape.
79
+ const request = sdk.request(flow, {
80
+ signer: owner,
81
+ inputs: {
82
+ amountIn: materialisers.directDeposit({ amount: '1000000000' }),
83
+ recipient,
84
+ },
85
+ });
86
+
87
+ return { flow, request };
88
+ };
@@ -0,0 +1,58 @@
1
+ import type { ComposeRouteRequest } from '@lifi/compose-spec';
2
+
3
+ import { routeAmount } from '../index.js';
4
+
5
+ import { OWNER } from './config.js';
6
+
7
+ const NATIVE_ETH = '0x0000000000000000000000000000000000000000';
8
+ const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
9
+
10
+ /**
11
+ * Wrap 0.1 native ETH into WETH on Ethereum mainnet through
12
+ * `POST /compose/route`.
13
+ *
14
+ * Demonstrates:
15
+ * - The convenience path: a from/to token pair instead of an authored flow.
16
+ * The pair resolves to the catalog's `wrap` edge, and the server authors the
17
+ * zap step for it
18
+ * - `routeAmount.exact` for a fixed input amount (`bigint`, serialised on send)
19
+ * - The native sentinel address as `fromToken`
20
+ *
21
+ * Pure: returns a plain {@link ComposeRouteRequest}, performs no network I/O.
22
+ * Pass it to `sdk.route(...)` / `sdk.client.route(...)` to compile it.
23
+ */
24
+ export const buildRouteExactExample = (): ComposeRouteRequest => ({
25
+ chainId: 1,
26
+ fromToken: NATIVE_ETH,
27
+ toToken: WETH,
28
+ amount: routeAmount.exact(100_000_000_000_000_000n), // 0.1 ETH
29
+ signer: OWNER,
30
+ slippageBps: 100,
31
+ });
32
+
33
+ /**
34
+ * Unwrap the signer's entire WETH balance back into native ETH on Ethereum
35
+ * mainnet — the catalog's `unwrap` edge.
36
+ *
37
+ * Demonstrates:
38
+ * - `routeAmount.all` for a balance resolved on-chain at execution time, where
39
+ * the passed value is only the quoting stand-in
40
+ * - `sweepTo` naming the address that receives the output
41
+ * - `checkOnChainAllowances` so already-sufficient approvals are omitted from
42
+ * the response
43
+ *
44
+ * Pure: returns a plain {@link ComposeRouteRequest}, performs no network I/O.
45
+ */
46
+ export const buildRouteAllExample = (): ComposeRouteRequest => ({
47
+ chainId: 1,
48
+ fromToken: WETH,
49
+ toToken: NATIVE_ETH,
50
+ // Estimate of the balance on hand — the quote and the derived preconditions
51
+ // are computed against this, but the amount actually moved is the real
52
+ // balance at execution time.
53
+ amount: routeAmount.all(10n ** 18n), // ~1 WETH
54
+ signer: OWNER,
55
+ sweepTo: OWNER,
56
+ slippageBps: 50,
57
+ checkOnChainAllowances: true,
58
+ });
@@ -20,6 +20,7 @@
20
20
  * split-zap — split USDC 60/40 and zap into Aave + Morpho vaults
21
21
  * split-arithmetic — split USDC 70/30 with arithmetic assertions
22
22
  * zap — zap USDC into Aave lending position
23
+ * zap-async — zap USDC into an async Aave position (future output)
23
24
  * swap-zap — swap WETH to USDC then zap into Aave
24
25
  * swap-check — swap WETH to USDC with balance check
25
26
  * swap-recipient — swap WETH + DAI to USDC and send to recipient
@@ -32,8 +33,13 @@
32
33
  * transfer — transfer full token balance to a recipient
33
34
  * partial-transfer — transfer a specific amount, keeping the remainder
34
35
  * untyped-ref — mix untypedOp with typed handles via raw.ref
36
+ * route-exact — wrap 0.1 native ETH into WETH via /compose/route
37
+ * route-all — unwrap the whole WETH balance to ETH via /compose/route
35
38
  */
36
- import type { ComposeCompileRequest } from '@lifi/compose-spec';
39
+ import type {
40
+ ComposeCompileRequest,
41
+ ComposeRouteRequest,
42
+ } from '@lifi/compose-spec';
37
43
 
38
44
  import { createComposeSdk } from '../sdk.js';
39
45
 
@@ -49,8 +55,13 @@ import { buildDepositFromProxy } from './depositFromProxy.js';
49
55
  import { buildDustSweepExample } from './dustSweep.js';
50
56
  import { buildLifiSwapExample } from './lifiSwap.js';
51
57
  import { buildLifiZapExample } from './lifiZap.js';
58
+ import { buildLifiZapAsyncExample } from './lifiZapAsync.js';
52
59
  import { buildRawCallWithArithmetic } from './rawCallWithArithmetic.js';
53
60
  import { buildReadContractState } from './readContractState.js';
61
+ import {
62
+ buildRouteAllExample,
63
+ buildRouteExactExample,
64
+ } from './routeTokenPair.js';
54
65
  import { buildSplitAndZapExample } from './splitAndZap.js';
55
66
  import { buildSplitWithArithmetic } from './splitWithArithmetic.js';
56
67
  import { buildSwapAndZapExample } from './swapAndZap.js';
@@ -106,6 +117,8 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
106
117
  'split-arithmetic': () =>
107
118
  buildSplitWithArithmetic({ owner: OWNER, amount: '1000000000' }).request,
108
119
  zap: () => buildLifiZapExample().request,
120
+ 'zap-async': () =>
121
+ buildLifiZapAsyncExample({ owner: OWNER, recipient: RECIPIENT }).request,
109
122
  'swap-zap': () => buildSwapAndZapExample().request,
110
123
  'swap-check': () =>
111
124
  buildSwapWithBalanceCheck({ owner: OWNER, amount: '1000000000000000000' })
@@ -146,6 +159,13 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
146
159
  'untyped-ref': () => buildUntypedOpWithTypedRef({ owner: OWNER }).request,
147
160
  };
148
161
 
162
+ // `/compose/route` examples build a `ComposeRouteRequest` and go to
163
+ // `sdk.route`, not `sdk.client.compile`, so they live in their own registry.
164
+ const ROUTE_EXAMPLES: Record<string, () => ComposeRouteRequest> = {
165
+ 'route-exact': buildRouteExactExample,
166
+ 'route-all': buildRouteAllExample,
167
+ };
168
+
149
169
  const run = async () => {
150
170
  const args = process.argv.slice(2);
151
171
  const useStaged = args.includes('--staged');
@@ -166,10 +186,13 @@ const run = async () => {
166
186
  examples = { ...EXAMPLES, ...stagedModule.STAGED_EXAMPLES };
167
187
  }
168
188
 
189
+ const routeBuilder = name ? ROUTE_EXAMPLES[name] : undefined;
169
190
  const builder = name ? examples[name] : undefined;
170
191
 
171
- if (!builder) {
172
- const valid = Object.keys(examples).join(', ');
192
+ if (!builder && !routeBuilder) {
193
+ const valid = [...Object.keys(examples), ...Object.keys(ROUTE_EXAMPLES)]
194
+ .sort()
195
+ .join(', ');
173
196
  console.error(
174
197
  `Error: ${
175
198
  name ? `unknown example "${name}"` : 'no example specified'
@@ -181,7 +204,7 @@ const run = async () => {
181
204
  }
182
205
  console.log(`Running example: ${name}`);
183
206
 
184
- const request = builder();
207
+ const request = routeBuilder ? routeBuilder() : builder!();
185
208
  const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
186
209
 
187
210
  console.log('--- Request ---');
@@ -195,7 +218,9 @@ const run = async () => {
195
218
  );
196
219
 
197
220
  console.log(`\n--- Compiling against ${BASE_URL} ---`);
198
- const result = await sdk.client.compile(request);
221
+ const result = routeBuilder
222
+ ? await sdk.route(request as ComposeRouteRequest)
223
+ : await sdk.client.compile(request as ComposeCompileRequest);
199
224
 
200
225
  console.log('\n--- Result ---');
201
226
  console.log(JSON.stringify(result, null, 2));
@@ -203,9 +203,14 @@ export interface CoreCallConfig {
203
203
  )[]
204
204
  )[]
205
205
  )[];
206
- approvals?: {
207
- spender: Address;
208
- }[];
206
+ approvals?: (
207
+ | {
208
+ spender: Address;
209
+ }
210
+ | {
211
+ from: 'sender';
212
+ }
213
+ )[];
209
214
  }
210
215
 
211
216
  export interface CoreStaticCallConfig {
@@ -684,6 +689,19 @@ export interface LifiZapConfig {
684
689
  };
685
690
  }
686
691
 
692
+ export interface LifiZapAsyncConfig {
693
+ resourceOut:
694
+ | {
695
+ kind: 'native';
696
+ chainId: Positive;
697
+ }
698
+ | {
699
+ kind: 'erc20';
700
+ token: Address;
701
+ chainId: Positive;
702
+ };
703
+ }
704
+
687
705
  export interface ParaswapBuyConfig {
688
706
  resourceOut:
689
707
  | {
@@ -1,10 +1,7 @@
1
1
  // AUTO-GENERATED from manifest.json — do not edit by hand.
2
2
  // Regenerate with: yarn workspace @lifi/composer-sdk generate
3
3
 
4
- import type {
5
- AnyBindable,
6
- FlowBuilderInternal,
7
- } from '../authoring/FlowBuilderCore.js';
4
+ import type { FlowBuilderInternal } from '../authoring/FlowBuilderCore.js';
8
5
  import type { Bindable, OutputHandle } from '../authoring/handles.js';
9
6
  import type {
10
7
  CoreCallResult,
@@ -47,6 +44,7 @@ import type {
47
44
  InvariantNumericConfig,
48
45
  InvariantNumericInRangeConfig,
49
46
  LifiSwapConfig,
47
+ LifiZapAsyncConfig,
50
48
  LifiZapConfig,
51
49
  MorphoBlueSupplyCollateralConfig,
52
50
  MorphoBlueWithdrawCollateralConfig,
@@ -520,11 +518,22 @@ export interface MorphoBlueWithdrawCollateralArgs {
520
518
  export interface LifiZapArgs {
521
519
  readonly bind: {
522
520
  readonly amountIn: Bindable<'resource'>;
521
+ readonly recipient?: Bindable<'address'>;
523
522
  };
524
523
  readonly config: LifiZapConfig;
525
524
  readonly guards?: readonly TypedGuard<'amountOut'>[];
526
525
  }
527
526
 
527
+ /** Route tokens into a DeFi protocol position whose output becomes available after the initiating transaction settles */
528
+ export interface LifiZapAsyncArgs {
529
+ readonly bind: {
530
+ readonly amountIn: Bindable<'resource'>;
531
+ readonly recipient: Bindable<'address'>;
532
+ };
533
+ readonly config: LifiZapAsyncConfig;
534
+ readonly guards?: readonly TypedGuard<never>[];
535
+ }
536
+
528
537
  /** 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`. */
529
538
  export interface ParaswapBuyArgs {
530
539
  readonly bind: {
@@ -629,6 +638,10 @@ export interface LifiZapOutputs {
629
638
  readonly amountOut: OutputHandle<'resource'>;
630
639
  }
631
640
 
641
+ export interface LifiZapAsyncOutputs {
642
+ readonly amountOut: OutputHandle<'resource'>;
643
+ }
644
+
632
645
  export interface ParaswapBuyOutputs {
633
646
  readonly amountOut: OutputHandle<'resource'>;
634
647
  readonly unspentIn: OutputHandle<'resource'>;
@@ -741,6 +754,11 @@ export type GeneratedOps = {
741
754
  readonly swap: (nodeId: string, args: LifiSwapArgs) => LifiSwapOutputs;
742
755
  /** Route tokens into a DeFi protocol position via the edge catalog — original id: "lifi.zap" */
743
756
  readonly zap: (nodeId: string, args: LifiZapArgs) => LifiZapOutputs;
757
+ /** Route tokens into a DeFi protocol position whose output becomes available after the initiating transaction settles — original id: "lifi.zapAsync" */
758
+ readonly zapAsync: (
759
+ nodeId: string,
760
+ args: LifiZapAsyncArgs,
761
+ ) => LifiZapAsyncOutputs;
744
762
  };
745
763
  readonly invariant: {
746
764
  /** Assert that an account holds at least a minimum token balance — original id: "invariant.balanceAtLeast" */
@@ -917,8 +935,8 @@ export const bindGeneratedOps = (
917
935
  call: <TSig extends string>(nodeId: string, args: CoreCallArgs<TSig>) => {
918
936
  const wire = buildCallWireFormat({
919
937
  resource: args.resource,
920
- bind: args.bind as Record<string, AnyBindable>,
921
- config: args.config as Record<string, unknown>,
938
+ bind: args.bind,
939
+ config: args.config,
922
940
  guards: args.guards,
923
941
  });
924
942
  return builder.call(nodeId, wire.op, wire, {
@@ -933,8 +951,8 @@ export const bindGeneratedOps = (
933
951
  nodeId,
934
952
  'core.staticCall',
935
953
  buildStaticCallWireFormat({
936
- bind: args.bind as Record<string, AnyBindable>,
937
- config: args.config as Record<string, unknown>,
954
+ bind: args.bind,
955
+ config: args.config,
938
956
  guards: args.guards,
939
957
  }),
940
958
  { result: 'uint256' } as const,
@@ -960,6 +978,10 @@ export const bindGeneratedOps = (
960
978
  builder.call(nodeId, 'lifi.zap', args, {
961
979
  amountOut: 'resource',
962
980
  } as const),
981
+ zapAsync: (nodeId: string, args: LifiZapAsyncArgs) =>
982
+ builder.call(nodeId, 'lifi.zapAsync', args, {
983
+ amountOut: 'resource',
984
+ } as const),
963
985
  },
964
986
  invariant: {
965
987
  balanceAtLeast: (nodeId: string, args: InvariantBalanceAtLeastArgs) =>
package/src/index.ts CHANGED
@@ -41,6 +41,7 @@ export type {
41
41
  ComposeCompileSuccessData,
42
42
  ComposeContinuation,
43
43
  ComposeErrorKind,
44
+ ComposeRouteRequest,
44
45
  ComposeTransactionRequest,
45
46
  FailedPreparedOp,
46
47
  Flow,
@@ -52,6 +53,9 @@ export type {
52
53
  PriceImpact,
53
54
  Ref,
54
55
  Resource,
56
+ RouteAmount,
57
+ RouteAmountAll,
58
+ RouteAmountExact,
55
59
  SimulationPolicy,
56
60
  SimulationRevert,
57
61
  SolType,
@@ -108,6 +112,7 @@ import * as materialisersMod from './generated/materialisers.generated.js';
108
112
  import * as preconditionsMod from './generated/preconditions.generated.js';
109
113
  import * as rawMod from './raw.js';
110
114
  import * as resourcesMod from './resources.js';
115
+ import * as routeAmountMod from './routeAmount.js';
111
116
 
112
117
  /**
113
118
  * Guard factories for protecting operation outputs (e.g. slippage tolerance).
@@ -168,6 +173,24 @@ export const preconditions: typeof preconditionsMod = preconditionsMod;
168
173
  * ```
169
174
  */
170
175
  export const resources: typeof resourcesMod = resourcesMod;
176
+ /**
177
+ * Amount constructors for {@link ComposeSdk.route}. Use
178
+ * {@link routeAmount.exact} to spend a fixed amount of the `fromToken`, and
179
+ * {@link routeAmount.all} to spend the signer's whole balance.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * // USDC into the Aave v3 aEthUSDC position — one routing-catalog edge.
184
+ * const result = await sdk.route({
185
+ * chainId: 1,
186
+ * fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
187
+ * toToken: '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c',
188
+ * amount: routeAmount.exact(1_000_000n),
189
+ * signer: '0xYourAddress...',
190
+ * });
191
+ * ```
192
+ */
193
+ export const routeAmount: typeof routeAmountMod = routeAmountMod;
171
194
  export * from './generated/config.generated.js';
172
195
 
173
196
  /**
@@ -0,0 +1,61 @@
1
+ import type { RouteAmountAll, RouteAmountExact } from '@lifi/compose-spec';
2
+
3
+ // Constructors for the `amount` member of a `ComposeRouteRequest`. The wire
4
+ // union is discriminated by `type` and — deliberately, because the two variants
5
+ // mean different things — names its payload field differently in each variant:
6
+ // `amount` is the sum actually moved, `simAmount` is only a quoting stand-in for
7
+ // a balance resolved on-chain. These helpers keep call sites from having to
8
+ // remember which name goes with which discriminant.
9
+
10
+ /**
11
+ * Spend an exact amount of the route's `fromToken`.
12
+ *
13
+ * @param amount - Amount in the token's smallest unit. `bigint` is serialised
14
+ * to a decimal string when the request is sent.
15
+ * @returns The `EXACT` member of the route amount union.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const result = await sdk.route({
20
+ * chainId: 1,
21
+ * fromToken: NATIVE_ETH,
22
+ * toToken: WETH,
23
+ * amount: routeAmount.exact(10n ** 17n), // 0.1 ETH, wrapped
24
+ * signer: OWNER,
25
+ * });
26
+ * ```
27
+ */
28
+ export const exact = (amount: bigint | string): RouteAmountExact => ({
29
+ type: 'EXACT',
30
+ amount,
31
+ });
32
+
33
+ /**
34
+ * Spend the signer's entire `fromToken` balance, resolved on-chain at execution
35
+ * time.
36
+ *
37
+ * Not supported when `fromToken` is the chain's native sentinel — the server
38
+ * rejects it. A gas coin is sent via `msg.value`, so there is no on-chain
39
+ * balance to sweep; use {@link exact} for native inputs.
40
+ *
41
+ * @param simAmount - A realistic estimate of that balance, in the token's
42
+ * smallest unit. The server quotes and simulates against this value, so it
43
+ * shapes the returned quote and preconditions — but the amount actually moved
44
+ * is whatever the balance turns out to be.
45
+ * @returns The `ALL` member of the route amount union.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const result = await sdk.route({
50
+ * chainId: 1,
51
+ * fromToken: USDC,
52
+ * toToken: A_ETH_USDC,
53
+ * amount: routeAmount.all(1_000_000n), // estimate: ~1 USDC on hand
54
+ * signer: OWNER,
55
+ * });
56
+ * ```
57
+ */
58
+ export const all = (simAmount: bigint | string): RouteAmountAll => ({
59
+ type: 'ALL',
60
+ simAmount,
61
+ });
package/src/sdk.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ComposeCompileRequest,
3
3
  ComposeCompileResult,
4
+ ComposeRouteRequest,
4
5
  InputSpec,
5
6
  SimulateRequest,
6
7
  SimulateResult,
@@ -119,6 +120,21 @@ export interface ComposeSdk {
119
120
  flow: TypedFlow<T>,
120
121
  run: ComposeRunInput<T>,
121
122
  ) => ComposeCompileRequest;
123
+ /**
124
+ * Compiles a token pair on one chain — as of today a single zap step over
125
+ * one routing-catalog edge. Thin pass-through to
126
+ * {@link ComposeClient.route}; see it for the current single-step limit,
127
+ * when to prefer `flow(...).compile(...)`, the `success` / `partial`
128
+ * discrimination, and error behaviour. Build the `amount` with
129
+ * `routeAmount.exact(...)` / `routeAmount.all(...)`.
130
+ *
131
+ * @param request - The from/to token pair, amount, signer, and route options.
132
+ * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.
133
+ * @throws {@link ComposeError} on network, validation, or server errors.
134
+ */
135
+ readonly route: (
136
+ request: ComposeRouteRequest,
137
+ ) => Promise<ComposeCompileResult>;
122
138
  /**
123
139
  * Simulates a raw, pre-encoded transaction. Thin pass-through to
124
140
  * {@link ComposeClient.simulate}; see it for the `ok` / `revert` / `error`
@@ -188,6 +204,7 @@ export const createComposeSdk = (options: ComposeSdkOptions): ComposeSdk => {
188
204
  client: composeClient,
189
205
  flow,
190
206
  request,
207
+ route: composeClient.route,
191
208
  simulate: composeClient.simulate,
192
209
  };
193
210
  };