Skip to content

Commit a2de8de

Browse files
committed
feat: useSupplySwap POC
1 parent 163cb8c commit a2de8de

File tree

1 file changed

+198
-35
lines changed

1 file changed

+198
-35
lines changed

packages/react/src/swap.ts

Lines changed: 198 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
import {
22
type CurrencyQueryOptions,
33
DEFAULT_QUERY_OPTIONS,
4-
errAsync,
54
ValidationError,
65
} from '@aave/client';
76
import {
87
cancelSwap,
8+
preparePositionSwap,
99
prepareSwap,
1010
prepareSwapCancel,
11+
supplySwapQuote,
1112
swap,
1213
swapQuote,
1314
swapStatus,
1415
} from '@aave/client/actions';
15-
import type {
16-
CancelError,
17-
SigningError,
18-
TimeoutError,
19-
TransactionError,
16+
import {
17+
type CancelError,
18+
type SigningError,
19+
type TimeoutError,
20+
type TransactionError,
2021
UnexpectedError,
2122
} from '@aave/core';
2223
import type {
2324
InsufficientBalanceError,
2425
PaginatedUserSwapsResult,
26+
PositionSwapApproval,
27+
PrepareSupplySwapRequest,
2528
PrepareSwapCancelRequest,
26-
SwapByIntentTypedData,
2729
SwapByIntentWithApprovalRequired,
2830
SwapCancelled,
2931
SwapExecutionPlan,
@@ -34,24 +36,28 @@ import type {
3436
UserSwapsRequest,
3537
} from '@aave/graphql';
3638
import {
37-
type CancelSwapTypedData,
3839
type ERC20PermitSignature,
40+
type PreparePositionSwapRequest,
3941
type PrepareTokenSwapRequest,
4042
type SwapApprovalRequired,
43+
type SwapByIntent,
4144
SwappableTokensQuery,
4245
type SwappableTokensRequest,
4346
SwapQuoteQuery,
47+
type SwapTypedData,
4448
type Token,
4549
type TransactionRequest,
4650
UserSwapsQuery,
4751
} from '@aave/graphql';
4852
import {
4953
invariant,
54+
isSignature,
5055
type NullishDeep,
5156
okAsync,
5257
type Prettify,
53-
type ResultAsync,
58+
ResultAsync,
5459
ResultAwareError,
60+
type Signature,
5561
} from '@aave/types';
5662
import { useCallback } from 'react';
5763
import { useAaveClient } from './context';
@@ -404,22 +410,179 @@ export function useUserSwaps({
404410
});
405411
}
406412

407-
export type UseSwapTokensRequest = Prettify<
413+
// ------------------------------------------------------------
414+
415+
export type SwapHandlerOptions = {
416+
cancel: CancelOperation;
417+
};
418+
419+
// ------------------------------------------------------------
420+
421+
/**
422+
* @experimental
423+
*/
424+
export type UseSwapSignerRequest = TransactionRequest; // TODO add other types to this union
425+
426+
/**
427+
* @experimental
428+
*/
429+
export type SwapSignerError = CancelError | SigningError | UnexpectedError;
430+
431+
/**
432+
* @experimental
433+
*/
434+
export type UseSwapSignerResult = UseAsyncTask<
435+
UseSwapSignerRequest,
436+
PendingTransaction | Signature,
437+
SwapSignerError
438+
>;
439+
440+
// ------------------------------------------------------------
441+
442+
export type PositionSwapPlan = PositionSwapApproval | SwapByIntent;
443+
444+
export type PositionSwapHandler = (
445+
plan: PositionSwapPlan,
446+
options: SwapHandlerOptions,
447+
) => ResultAsync<PendingTransaction | Signature, SwapSignerError>;
448+
449+
export type PositionSwapValue = {
450+
quote?: SwapQuote;
451+
};
452+
453+
// ------------------------------------------------------------
454+
455+
/**
456+
* @experimental
457+
*/
458+
export type UseSupplySwapRequest = Prettify<
459+
PrepareSupplySwapRequest & CurrencyQueryOptions
460+
>;
461+
462+
/**
463+
* @experimental
464+
*/
465+
export function useSupplySwap(
466+
handler: PositionSwapHandler,
467+
): UseAsyncTask<
468+
PrepareSupplySwapRequest,
469+
SwapReceipt,
470+
| SwapSignerError
471+
| SendTransactionError
472+
| PendingTransactionError
473+
| ValidationError<InsufficientBalanceError>
474+
> {
475+
const client = useAaveClient();
476+
477+
const processApprovals = useCallback(
478+
(approvals: PositionSwapApproval[]) => {
479+
return ResultAsync.combine(
480+
approvals.map((approval) =>
481+
handler(approval, { cancel }).map((value) => ({
482+
__typename: approval.__typename,
483+
value,
484+
})),
485+
),
486+
);
487+
},
488+
[handler],
489+
);
490+
491+
return useAsyncTask(
492+
({
493+
currency = DEFAULT_QUERY_OPTIONS.currency,
494+
...request
495+
}: UseSupplySwapRequest) => {
496+
return supplySwapQuote(client, request, { currency }).andThen(
497+
(result) => {
498+
invariant(
499+
result.__typename === 'PositionSwapByIntentApprovalsRequired',
500+
'Unsupported swap plan',
501+
);
502+
503+
return processApprovals(result.approvals)
504+
.map(
505+
(results): PreparePositionSwapRequest =>
506+
results.reduce(
507+
(
508+
request: PreparePositionSwapRequest,
509+
{ __typename, value },
510+
) => {
511+
if (value) {
512+
switch (__typename) {
513+
case 'PositionSwapAdapterContractApproval':
514+
request.adapterContractSignature = isSignature(value)
515+
? value
516+
: null;
517+
break;
518+
case 'PositionSwapPositionManagerApproval':
519+
request.positionManagerSignature = isSignature(value)
520+
? value
521+
: null;
522+
break;
523+
}
524+
}
525+
return request;
526+
},
527+
{
528+
quoteId: result.quote.quoteId,
529+
adapterContractSignature: null,
530+
positionManagerSignature: null,
531+
},
532+
),
533+
)
534+
.andThen((request) =>
535+
preparePositionSwap(client, request, { currency }),
536+
)
537+
.andThen((result) => {
538+
invariant(
539+
result.__typename === 'SwapByIntent',
540+
`Unsupported swap plan: ${result.__typename}. Upgrade to a newer version of the @aave/react package.`,
541+
);
542+
543+
return handler(result, { cancel });
544+
})
545+
.andThen((signature) => {
546+
invariant(isSignature(signature), 'Invalid signature');
547+
548+
return swap(client, {
549+
intent: {
550+
quoteId: result.quote.quoteId,
551+
signature,
552+
},
553+
});
554+
})
555+
.andThen((plan) => {
556+
switch (plan.__typename) {
557+
case 'SwapReceipt':
558+
return okAsync(plan);
559+
case 'InsufficientBalanceError':
560+
return ValidationError.fromGqlNode(plan).asResultAsync();
561+
default:
562+
return UnexpectedError.from(plan).asResultAsync();
563+
}
564+
});
565+
},
566+
);
567+
},
568+
[client, handler, processApprovals],
569+
);
570+
}
571+
572+
// ------------------------------------------------------------
573+
574+
export type UseTokenSwapRequest = Prettify<
408575
PrepareTokenSwapRequest & CurrencyQueryOptions
409576
>;
410577

411-
export type SwapIntent =
412-
| SwapByIntentTypedData
578+
export type TokenSwapPlan =
579+
| SwapTypedData
413580
| SwapByIntentWithApprovalRequired
414581
| SwapTransactionRequest
415582
| SwapApprovalRequired;
416583

417-
export type SwapHandlerOptions = {
418-
cancel: CancelOperation;
419-
};
420-
421-
export type SwapHandler = (
422-
intent: SwapIntent,
584+
export type TokenSwapHandler = (
585+
plan: TokenSwapPlan,
423586
options: SwapHandlerOptions,
424587
) => ResultAsync<
425588
ERC20PermitSignature | SwapReceipt,
@@ -445,9 +608,9 @@ function isERC20PermitSignature(
445608
* const [sendTransaction, sending] = useSendTransaction(wallet);
446609
* const [signSwapByIntentWith, signing] = useSignSwapByIntentWith(wallet);
447610
*
448-
* const [swap, swapping] = useSwapTokens((plan) => {
611+
* const [swap, swapping] = useTokenSwap((plan) => {
449612
* switch (plan.__typename) {
450-
* case 'SwapByIntentTypedData':
613+
* case 'SwapTypedData':
451614
* return signSwapByIntentWith(plan);
452615
*
453616
* case 'SwapApprovalRequired':
@@ -478,10 +641,10 @@ function isERC20PermitSignature(
478641
* // result.value: SwapReceipt
479642
* ```
480643
*/
481-
export function useSwapTokens(
482-
handler: SwapHandler,
644+
export function useTokenSwap(
645+
handler: TokenSwapHandler,
483646
): UseAsyncTask<
484-
PrepareTokenSwapRequest,
647+
UseTokenSwapRequest,
485648
SwapReceipt,
486649
| SendTransactionError
487650
| PendingTransactionError
@@ -517,7 +680,7 @@ export function useSwapTokens(
517680
return okAsync(plan.originalTransaction.orderReceipt);
518681
});
519682
case 'InsufficientBalanceError':
520-
return errAsync(ValidationError.fromGqlNode(plan));
683+
return ValidationError.fromGqlNode(plan).asResultAsync();
521684
case 'SwapReceipt':
522685
return okAsync(plan);
523686
}
@@ -529,7 +692,7 @@ export function useSwapTokens(
529692
({
530693
currency = DEFAULT_QUERY_OPTIONS.currency,
531694
...request
532-
}: UseSwapTokensRequest) =>
695+
}: UseTokenSwapRequest) =>
533696
prepareSwap(client, request, { currency }).andThen((preparePlan) => {
534697
switch (preparePlan.__typename) {
535698
case 'SwapByTransaction':
@@ -575,15 +738,15 @@ export function useSwapTokens(
575738
});
576739

577740
case 'InsufficientBalanceError':
578-
return errAsync(ValidationError.fromGqlNode(preparePlan));
741+
return ValidationError.fromGqlNode(preparePlan).asResultAsync();
579742
}
580743
}),
581744
[client, handler, executeSwap],
582745
);
583746
}
584747

585748
export type CancelSwapHandler = (
586-
data: CancelSwapTypedData | TransactionRequest,
749+
data: SwapTypedData | TransactionRequest,
587750
) => ResultAsync<
588751
ERC20PermitSignature | PendingTransaction,
589752
SigningError | UnexpectedError
@@ -609,12 +772,12 @@ export type CancelSwapError =
609772
* const [sendTransaction] = useSendTransaction(wallet);
610773
* const [signSwapCancelWith] = useSignSwapCancelWith(wallet);
611774
*
612-
* const [cancelSwap, {loading, error}] = useCancelSwap((plan: CancelSwapTypedData | TransactionRequest) => {
775+
* const [cancelSwap, {loading, error}] = useCancelSwap((plan: SwapTypedData | TransactionRequest) => {
613776
* switch (plan.__typename) {
614777
* case 'TransactionRequest':
615778
* return sendTransaction(plan);
616779
*
617-
* case 'CancelSwapTypedData':
780+
* case 'SwapTypedData':
618781
* return signSwapCancelWith(plan);
619782
* }
620783
* });
@@ -670,9 +833,9 @@ export function useCancelSwap(
670833
if (status.__typename === 'SwapCancelled') {
671834
return okAsync(status);
672835
}
673-
return errAsync(
674-
new CannotCancelSwapError('Failed to cancel swap'),
675-
);
836+
return new CannotCancelSwapError(
837+
'Failed to cancel swap',
838+
).asResultAsync();
676839
})
677840
);
678841
});
@@ -681,9 +844,9 @@ export function useCancelSwap(
681844
return okAsync(status);
682845

683846
default:
684-
return errAsync(
685-
new CannotCancelSwapError('Swap cannot longer be cancelled'),
686-
);
847+
return new CannotCancelSwapError(
848+
'Swap cannot longer be cancelled',
849+
).asResultAsync();
687850
}
688851
}),
689852
[client, handler],

0 commit comments

Comments
 (0)