Skip to content

Commit 9d1ac08

Browse files
committed
feat: implements sendWith helpers for ethers and viem
1 parent 31b5243 commit 9d1ac08

File tree

3 files changed

+167
-5
lines changed

3 files changed

+167
-5
lines changed

packages/client/src/ethers.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,71 @@
1-
export {};
1+
import type {
2+
ExecutionPlan,
3+
InsufficientBalanceError,
4+
TransactionRequest,
5+
} from '@aave/graphql';
6+
import {
7+
errAsync,
8+
invariant,
9+
nonNullable,
10+
ResultAsync,
11+
type TxHash,
12+
txHash,
13+
} from '@aave/types';
14+
import type { Signer, TransactionResponse } from 'ethers';
15+
import { SigningError, ValidationError } from './errors';
16+
import type { ExecutionPlanHandler } from './types';
17+
18+
async function sendTransaction(
19+
signer: Signer,
20+
request: TransactionRequest,
21+
): Promise<TransactionResponse> {
22+
return signer.sendTransaction({
23+
to: request.to,
24+
data: request.data,
25+
value: request.value,
26+
from: request.from,
27+
});
28+
}
29+
30+
/**
31+
* @internal
32+
*/
33+
export function sendTransactionAndWait(
34+
signer: Signer,
35+
request: TransactionRequest,
36+
): ResultAsync<TxHash, SigningError> {
37+
return ResultAsync.fromPromise(sendTransaction(signer, request), (err) =>
38+
SigningError.from(err),
39+
)
40+
.map((tx) => tx.wait())
41+
.map((receipt) => txHash(nonNullable(receipt?.hash)));
42+
}
43+
44+
/**
45+
* Sends transactions using the provided ethers signer.
46+
*
47+
* Handles {@link TransactionRequest} by signing and sending, {@link ApprovalRequired} by sending both approval and original transactions, and returns validation errors for {@link InsufficientBalanceError}.
48+
*/
49+
export function sendWith(signer: Signer | undefined): ExecutionPlanHandler {
50+
return (
51+
result: ExecutionPlan,
52+
): ResultAsync<
53+
TxHash,
54+
SigningError | ValidationError<InsufficientBalanceError>
55+
> => {
56+
invariant(signer, 'Expected a Signer to handle the operation result.');
57+
58+
switch (result.__typename) {
59+
case 'TransactionRequest':
60+
return sendTransactionAndWait(signer, result);
61+
62+
case 'ApprovalRequired':
63+
return sendTransactionAndWait(signer, result.approval).andThen(() =>
64+
sendTransactionAndWait(signer, result.originalTransaction),
65+
);
66+
67+
case 'InsufficientBalanceError':
68+
return errAsync(ValidationError.fromGqlNode(result));
69+
}
70+
};
71+
}

packages/client/src/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { ExecutionPlan, TransactionRequest } from '@aave/graphql';
1+
import type { ExecutionPlan, InsufficientBalanceError } from '@aave/graphql';
22
import type { ResultAsync, TxHash } from '@aave/types';
33
import type { SigningError, ValidationError } from './errors';
44

5-
export type OperationHandler<T extends ExecutionPlan = ExecutionPlan> = (
5+
export type ExecutionPlanHandler<T extends ExecutionPlan = ExecutionPlan> = (
66
result: T,
77
) => ResultAsync<
88
TxHash,
9-
SigningError | ValidationError<Exclude<T, TransactionRequest>>
9+
SigningError | ValidationError<InsufficientBalanceError>
1010
>;

packages/client/src/viem.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,93 @@
1-
export {};
1+
import type {
2+
ExecutionPlan,
3+
InsufficientBalanceError,
4+
TransactionRequest,
5+
} from '@aave/graphql';
6+
import {
7+
errAsync,
8+
invariant,
9+
ResultAsync,
10+
type TxHash,
11+
txHash,
12+
} from '@aave/types';
13+
import type { Hash, WalletClient } from 'viem';
14+
import {
15+
sendTransaction as sendEip1559Transaction,
16+
waitForTransactionReceipt,
17+
} from 'viem/actions';
18+
import { SigningError, ValidationError } from './errors';
19+
import type { ExecutionPlanHandler } from './types';
20+
21+
async function sendTransaction(
22+
walletClient: WalletClient,
23+
request: TransactionRequest,
24+
): Promise<Hash> {
25+
return sendEip1559Transaction(walletClient, {
26+
account: request.from,
27+
data: request.data,
28+
to: request.to,
29+
value: BigInt(request.value),
30+
chain: request.chainId,
31+
});
32+
}
33+
34+
/**
35+
* @internal
36+
*/
37+
export function sendTransactionAndWait(
38+
walletClient: WalletClient,
39+
request: TransactionRequest,
40+
): ResultAsync<TxHash, SigningError> {
41+
return ResultAsync.fromPromise(
42+
sendTransaction(walletClient, request),
43+
(err) => SigningError.from(err),
44+
)
45+
.map(async (hash) =>
46+
waitForTransactionReceipt(walletClient, {
47+
hash,
48+
pollingInterval: 100,
49+
retryCount: 20,
50+
retryDelay: 50,
51+
}),
52+
)
53+
.map((receipt) => txHash(receipt.transactionHash));
54+
}
55+
56+
/**
57+
* Sends transactions using the provided wallet client.
58+
*
59+
* Handles {@link TransactionRequest} by signing and sending, {@link ApprovalRequired} by sending both approval and original transactions, and returns validation errors for {@link InsufficientBalanceError}.
60+
*/
61+
export function sendWith(
62+
walletClient: WalletClient | undefined,
63+
): ExecutionPlanHandler {
64+
return (
65+
result: ExecutionPlan,
66+
): ResultAsync<
67+
TxHash,
68+
SigningError | ValidationError<InsufficientBalanceError>
69+
> => {
70+
if ('hash' in result) {
71+
return okAsync(result.hash);
72+
}
73+
74+
invariant(
75+
walletClient,
76+
'Expected a WalletClient to handle the operation result.',
77+
);
78+
79+
switch (result.__typename) {
80+
case 'TransactionRequest':
81+
return sendTransactionAndWait(walletClient, result);
82+
83+
case 'ApprovalRequired':
84+
return sendTransactionAndWait(walletClient, result.approval).andThen(
85+
() =>
86+
sendTransactionAndWait(walletClient, result.originalTransaction),
87+
);
88+
89+
case 'InsufficientBalanceError':
90+
return errAsync(ValidationError.fromGqlNode(result));
91+
}
92+
};
93+
}

0 commit comments

Comments
 (0)