Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

raps v2 #6138

Merged
merged 14 commits into from
Oct 1, 2024
45 changes: 45 additions & 0 deletions src/rapsV2/actions/claimTransactionClaimableAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ActionProps } from '../references';
import { sendTransaction } from '@/model/wallet';
import { getProvider } from '@/handlers/web3';
import { RainbowError } from '@/logger';
import { addNewTransaction } from '@/state/pendingTransactions';
import { NewTransaction } from '@/entities';
import { chainsName } from '@/chains';

export async function claimTransactionClaimable({ parameters, wallet }: ActionProps<'claimTransactionClaimableAction'>) {
// will uncomment actual logic in follow-up PR, don't worry about it for now
return { nonce: undefined, hash: undefined };

// const { claimTx } = parameters;

// const provider = getProvider({ chainId: claimTx.chainId });
// const result = await sendTransaction({ transaction: claimTx, existingWallet: wallet, provider });

// if (!result?.result || !!result.error || !result.result.hash) {
// throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: failed to execute claim transaction');
// }

// const transaction = {
// amount: result.result.value.toString(),
// gasLimit: result.result.gasLimit,
// from: result.result.from ?? null,
// to: result.result.to ?? null,
// chainId: result.result.chainId,
// hash: result.result.hash,
// network: chainsName[result.result.chainId],
// status: 'pending',
// type: 'send',
// nonce: result.result.nonce,
// } satisfies NewTransaction;

// addNewTransaction({
// address: claimTx.from,
// chainId: claimTx.chainId,
// transaction,
// });

// return {
// nonce: result.result.nonce,
// hash: result.result.hash,
// };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

necessary comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i uncomment in a followup pr that's currently open for review, i commented this code temporarily just so i could split my large branch into isolated PRs

}
1 change: 1 addition & 0 deletions src/rapsV2/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { claimTransactionClaimable } from './claimTransactionClaimableAction';
13 changes: 13 additions & 0 deletions src/rapsV2/claimTransactionClaimableRap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createNewAction, createNewRap } from './common';
import { RapAction, RapParameters } from './references';

export async function createClaimTransactionClaimableRap(parameters: Extract<RapParameters, { type: 'claimTransactionClaimableRap' }>) {
let actions: RapAction<'claimTransactionClaimableAction'>[] = [];

const claim = createNewAction('claimTransactionClaimableAction', parameters.claimTransactionClaimableActionParameters);
actions = actions.concat(claim);

// create the overall rap
const newRap = createNewRap(actions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could just be

const claim = createNewAction('claimTransactionClaimableAction', parameters.claimTransactionClaimableActionParameters);
const newRap = createNewRap([claim]);

if I'm remembering correctly concat. actions was for raps with 2 or more actions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah unnecessary for now but my next task after getting these merged is to add more actions to this rap so i think it's fine to keep it

return newRap;
}
20 changes: 20 additions & 0 deletions src/rapsV2/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { RapAction, RapActionParameterMap, RapActionTypes } from './references';

export interface RapActionTransaction {
hash: string | null;
}

export function createNewAction<T extends RapActionTypes>(type: T, parameters: RapActionParameterMap[T]): RapAction<T> {
const newAction = {
parameters,
transaction: { confirmed: null, hash: null },
type,
};
return newAction;
}

export function createNewRap<T extends RapActionTypes>(actions: RapAction<T>[]) {
return {
actions,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the same than old raps? feels like a unnecessary code duplication

121 changes: 121 additions & 0 deletions src/rapsV2/execute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-async-promise-executor */
/* eslint-disable no-promise-executor-return */
import { Signer } from '@ethersproject/abstract-signer';

import { RainbowError, logger } from '@/logger';

import { ActionProps, RapResponse, Rap, RapAction, RapActionResponse, RapActionTypes, RapParameters } from './references';
import { createClaimTransactionClaimableRap } from './claimTransactionClaimableRap';
import { claimTransactionClaimable } from './actions/claimTransactionClaimableAction';
import { delay } from '@/utils/delay';

// get the rap by type
export function createRap(parameters: RapParameters): Promise<{ actions: RapAction<RapActionTypes>[] }> {
switch (parameters.type) {
case 'claimTransactionClaimableRap':
return createClaimTransactionClaimableRap(parameters);
default:
return Promise.resolve({ actions: [] });
}
}

// get the action executable by type
function getActionExecutableByType<T extends RapActionTypes>(type: T, props: ActionProps<T>) {
switch (type) {
case 'claimTransactionClaimableAction':
return () => claimTransactionClaimable(props);
default:
throw new RainbowError(`[rapsV2/execute]: T - unknown action type ${type}`);
}
}

// executes a single action in the rap
// if the action executes a tx on-chain, it will return the nonce it used
// if an error occurs, we return the error message
export async function executeAction<T extends RapActionTypes>({
action,
wallet,
rap,
nonceToUse,
rapName,
}: {
action: RapAction<T>;
wallet: Signer;
rap: Rap;
nonceToUse: number | undefined;
rapName: string;
}): Promise<RapActionResponse> {
const { type, parameters } = action;
try {
const actionProps = {
wallet,
currentRap: rap,
parameters,
nonceToUse,
};
const { nonce, hash } = await getActionExecutableByType<T>(type, actionProps)();
return { nonce, errorMessage: null, hash };
} catch (error) {
logger.error(new RainbowError(`[rapsV2/execute]: ${rapName} - error execute action`), {
message: (error as Error)?.message,
});
return { nonce: null, errorMessage: String(error), hash: null };
}
}

function getRapFullName<T extends RapActionTypes>(actions: RapAction<T>[]) {
const actionTypes = actions.map(action => action.type);
return actionTypes.join(' + ');
}

const waitForNodeAck = async (hash: string, provider: Signer['provider']): Promise<void> => {
return new Promise(async resolve => {
const tx = await provider?.getTransaction(hash);
// This means the node is aware of the tx, we're good to go
if ((tx && tx.blockNumber === null) || (tx && tx?.blockNumber && tx?.blockNumber > 0)) {
resolve();
} else {
// Wait for 1 second and try again
await delay(1000);
return waitForNodeAck(hash, provider);
}
});
};

// goes through each action in the rap and executes it
// if an action executes a tx on-chain, increment the nonceToUse for the next tx
// if an action fails, it will return the error message
const executeRap = async (wallet: Signer, rap: Rap): Promise<RapResponse> => {
const { actions } = rap;
const rapName = getRapFullName(rap.actions);
let nonceToUse: number | undefined;

while (actions.length) {
const action = actions.shift();

if (!action) break;

const { nonce, errorMessage, hash } = await executeAction({
action,
wallet,
rap,
nonceToUse,
rapName,
});

if (errorMessage) return { errorMessage };

if (typeof nonce === 'number') {
actions.length >= 1 && hash && (await waitForNodeAck(hash, wallet.provider));
nonceToUse = nonce + 1;
}
}

return { errorMessage: null };
};

export async function walletExecuteRap(wallet: Signer, rapParameters: RapParameters): Promise<RapResponse> {
const rap = await createRap(rapParameters);
return executeRap(wallet, rap);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see a lot of this is a dupe from old raps, I understand there are types conflicts but there's no chance to avoid this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's definitely avoidable, i felt that it was safer and more clear to keep v1 and v2 isolated. i can understand why that could be confusing when trying to tell which code is new and which is old.

83 changes: 83 additions & 0 deletions src/rapsV2/references.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Signer } from '@ethersproject/abstract-signer';
import { TransactionRequest } from '@ethersproject/abstract-provider';

// supports legacy and new gas types
export type TransactionClaimableTxPayload = TransactionRequest &
(
| {
to: string;
from: string;
nonce: number;
gasLimit: string;
maxFeePerGas: string;
maxPriorityFeePerGas: string;
data: string;
value: '0x0';
chainId: number;
}
| {
to: string;
from: string;
nonce: number;
gasLimit: string;
gasPrice: string;
data: string;
value: '0x0';
chainId: number;
}
);

export interface ClaimTransactionClaimableActionParameters {
claimTx: TransactionClaimableTxPayload;
}

export interface RapActionTransaction {
hash: string | null;
}

export type RapActionParameterMap = {
claimTransactionClaimableAction: ClaimTransactionClaimableActionParameters;
};

export type RapParameters = {
type: 'claimTransactionClaimableRap';
claimTransactionClaimableActionParameters: ClaimTransactionClaimableActionParameters;
};

export interface RapAction<T extends RapActionTypes> {
parameters: RapActionParameterMap[T];
transaction: RapActionTransaction;
type: T;
}

export interface Rap {
actions: RapAction<'claimTransactionClaimableAction'>[];
}

export enum rapActions {
claimTransactionClaimableAction = 'claimTransactionClaimableAction',
}

export type RapActionTypes = keyof typeof rapActions;

export enum rapTypes {
claimTransactionClaimableRap = 'claimTransactionClaimableRap',
}

export type RapTypes = keyof typeof rapTypes;

export interface RapActionResponse {
nonce: number | null | undefined;
errorMessage: string | null;
hash: string | null | undefined;
}

export interface ActionProps<T extends RapActionTypes> {
nonceToUse: number | undefined;
parameters: RapActionParameterMap[T];
wallet: Signer;
}

export interface RapResponse {
errorMessage: string | null;
}
Loading