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

feat: add swap+send STX typings #4634

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
11 changes: 10 additions & 1 deletion packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,13 +444,21 @@ export type TransactionControllerTransactionNewSwapEvent = {
};

/**
* Represents the `TransactionController:transactionNewSwapApproval` event.
* Represents the `TransactionController:transactionNewSwapAndSend` event.
*/
export type TransactionControllerTransactionNewSwapAndSendEvent = {
type: `${typeof controllerName}:transactionNewSwapAndSend`;
payload: [{ transactionMeta: TransactionMeta }];
};

/**
* Represents the `TransactionController:transactionNewSwapAndSendApproval` event.
*/
export type TransactionControllerTransactionNewSwapAndSendApprovalEvent = {
type: `${typeof controllerName}:transactionNewSwapAndSendApproval`;
payload: [{ transactionMeta: TransactionMeta }];
};

/**
* Represents the `TransactionController:transactionPublishingSkipped` event.
*/
Expand Down Expand Up @@ -521,6 +529,7 @@ export type TransactionControllerEvents =
| TransactionControllerTransactionNewSwapApprovalEvent
| TransactionControllerTransactionNewSwapEvent
| TransactionControllerTransactionNewSwapAndSendEvent
| TransactionControllerTransactionNewSwapAndSendApprovalEvent
| TransactionControllerTransactionPublishingSkipped
| TransactionControllerTransactionRejectedEvent
| TransactionControllerTransactionStatusUpdatedEvent
Expand Down
22 changes: 22 additions & 0 deletions packages/transaction-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ export type TransactionMeta = TransactionMetaBase &
* Information about a single transaction such as status and block number.
*/
type TransactionMetaBase = {
/**
* Approval to be ran immediately before the transaction created with `txParams`.
*/
approvalTx?: {
data?: TransactionParams;
params?: {
requireApproval?: boolean;
type?: TransactionType;
swaps?: {
meta?: Partial<TransactionMeta>;
};
};
};

/**
* ID of the transaction that approved the swap token transfer.
*/
Expand Down Expand Up @@ -642,6 +656,14 @@ export enum TransactionType {
// eslint-disable-next-line @typescript-eslint/naming-convention
swapAndSend = 'swapAndSend',

/**
* At a contract level, identical to the swapApproval type but requested
* during the swap+send flow and only for the amount needed for the trade
*/
// TODO: Either fix this lint violation or explain why it's necessary to ignore.
// eslint-disable-next-line @typescript-eslint/naming-convention
swapAndSendApproval = 'swapAndSendApproval',

/**
* Similar to the approve type, a swap approval is a special case of ERC20
* approve method that requests an allowance of the token to spend on behalf
Expand Down
54 changes: 54 additions & 0 deletions packages/transaction-controller/src/utils/swaps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,60 @@ describe('updateSwapsTransaction', () => {
swapAndSendRecipient,
});
});

it('should update swap and send approval transaction and publish TransactionController:transactionNewSwapAndSendApproval', () => {
const sourceTokenSymbol = 'ETH';
const type = TransactionType.swapAndSendApproval;

swaps.meta = {
sourceTokenSymbol,
type,
};
transactionType = TransactionType.swapAndSendApproval;

const transactionNewSwapAndSendApprovalEventListener = jest.fn();
messenger.subscribe(
'TransactionController:transactionNewSwapAndSendApproval',
transactionNewSwapAndSendApprovalEventListener,
);

updateSwapsTransaction(transactionMeta, transactionType, swaps, request);
expect(
transactionNewSwapAndSendApprovalEventListener,
).toHaveBeenCalledTimes(1);
expect(transactionNewSwapAndSendApprovalEventListener).toHaveBeenCalledWith(
{
transactionMeta: {
...transactionMeta,
sourceTokenSymbol,
type,
},
},
);
});

it('should return the swap and send approval transaction updated with information', () => {
const sourceTokenSymbol = 'ETH';
const type = TransactionType.swapAndSendApproval;

swaps.meta = {
sourceTokenSymbol,
type,
};
transactionType = TransactionType.swapAndSendApproval;

const updatedSwapsTransaction = updateSwapsTransaction(
transactionMeta,
transactionType,
swaps,
request,
);
expect(updatedSwapsTransaction).toStrictEqual({
...transactionMeta,
sourceTokenSymbol,
type,
});
});
});

describe('updatePostTransactionBalance', () => {
Expand Down
22 changes: 21 additions & 1 deletion packages/transaction-controller/src/utils/swaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
export const SWAP_TRANSACTION_TYPES = [
TransactionType.swap,
TransactionType.swapAndSend,
TransactionType.swapAndSendApproval,
TransactionType.swapApproval,
];

Expand Down Expand Up @@ -193,6 +194,19 @@ export function updateSwapsTransaction(
});
}

if (transactionType === TransactionType.swapAndSendApproval) {
updatedTransactionMeta = updateSwapApprovalTransaction(
transactionMeta,
swapsMeta,
);
messenger.publish(
'TransactionController:transactionNewSwapAndSendApproval',
{
transactionMeta: updatedTransactionMeta,
},
);
}

if (transactionType === TransactionType.swap) {
updatedTransactionMeta = updateSwapTransaction(transactionMeta, swapsMeta);
messenger.publish('TransactionController:transactionNewSwap', {
Expand Down Expand Up @@ -343,6 +357,7 @@ function updateSwapTransaction(
*
* @param transactionMeta - Transaction meta object to update
* @param propsToUpdate - Properties to update
* @param propsToUpdate.approvalTx - The parameters of the approval transaction
* @param propsToUpdate.approvalTxId - Transaction id of the approval transaction
* @param propsToUpdate.destinationTokenAddress - Address of the token to be received
* @param propsToUpdate.destinationTokenAmount - The raw amount of the destination token
Expand All @@ -362,6 +377,7 @@ function updateSwapTransaction(
function updateSwapAndSendTransaction(
Copy link
Member

@matthewwalsh0 matthewwalsh0 Aug 22, 2024

Choose a reason for hiding this comment

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

If the purpose of the approvalTxParams is ultimately to be included in this event, could we instead just include them in the addTransaction method options and pass them to updateSwapsTransaction then here and then include them as an alternate property in the event?

That way the event has the info, but we don't have to persist anything new?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need the approval TX for the confirmations screen in extension, not just this method. The idea is that we ensure it's always bundled with the tx, so that we don't lose it if the send state in extension is lost (ie the user closes the extension and reopens it)

Copy link
Member

Choose a reason for hiding this comment

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

If the user closes the extension, then I believe we automatically reject the transaction.

transactionMeta: TransactionMeta,
{
approvalTx,
approvalTxId,
destinationTokenAddress,
destinationTokenAmount,
Expand All @@ -378,9 +394,13 @@ function updateSwapAndSendTransaction(
type,
}: Partial<TransactionMeta>,
): TransactionMeta {
validateIfTransactionUnapproved(transactionMeta, 'updateSwapTransaction');
validateIfTransactionUnapproved(
transactionMeta,
'updateSwapAndSendTransaction',
);

let swapTransaction = {
approvalTx,
approvalTxId,
destinationTokenAddress,
destinationTokenAmount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ describe('UserOperationController', () => {
smartContractAccount,
swaps: {
approvalTxId: 'testTxId',
approvalTx: { data: { from: 'from' } },
destinationTokenAmount: '0x1',
destinationTokenAddress: '0x1',
destinationTokenDecimals: 3,
Expand All @@ -445,6 +446,7 @@ describe('UserOperationController', () => {
expect.objectContaining({
swapsMetadata: {
approvalTxId: 'testTxId',
approvalTx: { data: { from: 'from' } },
destinationTokenAmount: '0x1',
destinationTokenAddress: '0x1',
destinationTokenDecimals: 3,
Expand Down Expand Up @@ -480,6 +482,7 @@ describe('UserOperationController', () => {
expect.objectContaining({
swapsMetadata: {
approvalTxId: null,
approvalTx: null,
destinationTokenAddress: null,
destinationTokenAmount: null,
destinationTokenDecimals: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ export type AddUserOperationRequest = {
};

export type AddUserOperationSwapOptions = {
approvalTx?: {
data: TransactionParams;
params?: {
requireApproval: boolean;
type: TransactionType;
swaps?: {
meta?: Partial<TransactionMeta>;
};
};
};
approvalTxId?: string;
destinationTokenAddress?: string;
destinationTokenAmount?: string;
Expand Down Expand Up @@ -446,6 +456,7 @@ export class UserOperationController extends BaseController<
status: UserOperationStatus.Unapproved,
swapsMetadata: swaps
? {
approvalTx: swaps.approvalTx ?? null,
approvalTxId: swaps.approvalTxId ?? null,
destinationTokenAddress: swaps.destinationTokenAddress ?? null,
destinationTokenAmount: swaps.destinationTokenAmount ?? null,
Expand Down
13 changes: 13 additions & 0 deletions packages/user-operation-controller/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
TransactionMeta,
TransactionParams,
TransactionType,
UserFeeLevel,
Expand Down Expand Up @@ -336,6 +337,18 @@ export type UserOperationReceipt = {

/** Information specific to user operations created from swap transactions. */
export type SwapsMetadata = {
/** Transaction data for approval to be ran immediately before the swap transaction */
approvalTx?: {
data: TransactionParams;
params?: {
requireApproval?: boolean;
type?: TransactionType;
swaps?: {
meta?: Partial<TransactionMeta>;
};
};
} | null;

/** ID of the associated approval transaction. */
approvalTxId: string | null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ describe('transation', () => {
...USER_OPERATION_METADATA_MOCK,
swapsMetadata: {
approvalTxId: 'testTxId',
approvalTx: { test: 'value' },
destinationTokenAddress: '0x2',
destinationTokenDecimals: '3',
destinationTokenSymbol: 'TEST',
Expand All @@ -274,6 +275,7 @@ describe('transation', () => {
expect(transactionMetadata).toStrictEqual(
expect.objectContaining({
approvalTxId: 'testTxId',
approvalTx: { test: 'value' },
destinationTokenAddress: '0x2',
destinationTokenDecimals: '3',
destinationTokenSymbol: 'TEST',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export function getTransactionMetadata(
delete txParams.gasPrice;

const swaps = {
approvalTx: swapsMetadata?.approvalTx ?? undefined,
approvalTxId: swapsMetadata?.approvalTxId ?? undefined,
destinationTokenAddress:
swapsMetadata?.destinationTokenAddress ?? undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ describe('validation', () => {
'type',
'wrong type',
123,
'Expected one of `"cancel","contractInteraction","contractDeployment","eth_decrypt","eth_getEncryptionPublicKey","incoming","personal_sign","retry","simpleSend","eth_signTypedData","smart","swap","swapAndSend","swapApproval","approve","safetransferfrom","transfer","transferfrom","setapprovalforall","increaseAllowance"`, but received: 123',
'Expected one of `"cancel","contractInteraction","contractDeployment","eth_decrypt","eth_getEncryptionPublicKey","incoming","personal_sign","retry","simpleSend","eth_signTypedData","smart","swap","swapAndSend","swapAndSendApproval","swapApproval","approve","safetransferfrom","transfer","transferfrom","setapprovalforall","increaseAllowance"`, but received: 123',
],
])(
'throws if %s is %s',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function validateAddUserOperationOptions(
),
swaps: optional(
object({
approvalTx: optional(object()),
approvalTxId: optional(string()),
destinationTokenAddress: optional(string()),
destinationTokenDecimals: optional(number()),
Expand Down
Loading