@lifi/composer-sdk
Version:
Public Composer SDK for building and submitting flows
96 lines (83 loc) • 2.97 kB
text/typescript
import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
import { createComposeSdk, materialisers, resources } from '../index.js';
import type { Address } from '../types.js';
import { BASE_URL } from './config.js';
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
export interface InvariantChecksInput {
readonly owner: Address;
readonly spender: Address;
readonly amount: `${bigint}`;
readonly minAmountOut: `${bigint}`;
}
/**
* Swap WETH → USDC via LI.FI, then layer two of the newly-exposed invariant
* ops on the result.
*
* Demonstrates:
* - `invariant.numeric` asserting the swap output is at least a constant
* threshold (`amountOut >= minAmountOut`) — a constant-threshold check that
* needs no second on-chain value to compare against.
* - `invariant.allowanceAtLeast` asserting the execution proxy still permits
* `spender` to move at least the swapped USDC on its behalf.
* - Binding a swap's `amountOut` resource handle into both a `uint256` numeric
* slot and a `resource` allowance slot.
*
* Both ops compile to on-chain `AssertRawInvariant` instructions; the run
* reverts if either assertion is violated.
*/
export const buildInvariantChecks = ({
owner,
spender,
amount,
minAmountOut,
}: InvariantChecksInput): {
flow: Flow;
request: ComposeCompileRequest;
} => {
const sdk = createComposeSdk({ baseUrl: BASE_URL });
// One WETH resource input plus a scalar spender address.
const builder = sdk.flow(1, {
name: 'invariant-checks',
inputs: {
amountIn: resources.erc20(WETH, 1),
spender: 'address',
},
});
// Swap WETH → USDC via LI.FI.
const swapOutputs = builder.lifi.swap('swap', {
bind: { amountIn: builder.inputs.amountIn },
config: {
resourceOut: resources.erc20(USDC, 1),
slippage: 0.03,
},
});
// Assert the swap produced at least `minAmountOut` USDC. The threshold is a
// configured constant, so this is `invariant.numeric` rather than a
// handle-vs-handle compare.
builder.invariant.numeric('min-out', {
bind: { value: swapOutputs.amountOut },
config: { op: 'gte', threshold: minAmountOut },
});
// Assert the execution proxy still grants `spender` an allowance of at least
// the swapped USDC amount.
builder.invariant.allowanceAtLeast('check-allowance', {
bind: {
minimumAmount: swapOutputs.amountOut,
owner: builder.context.executionAddress,
spender: builder.inputs.spender,
},
});
const flow = builder.build();
// directDeposit marks amountIn as a fixed amount of WETH pre-deposited into
// the VM; the spender address is supplied as a plain scalar input.
const request = sdk.request(flow, {
signer: owner,
inputs: {
amountIn: materialisers.directDeposit({ amount }),
spender,
},
sweepTo: builder.context.sender,
});
return { flow, request };
};