/ src / lib / components / stepper / types.ts
types.ts
  1  /* eslint-disable @typescript-eslint/no-explicit-any */
  2  import type { SendTransactionsResponse } from '@safe-global/safe-apps-sdk';
  3  import type { TransactionLike, TypedDataDomain, TypedDataField } from 'ethers';
  4  import type { TransactionReceipt } from 'ethers';
  5  import type { Component, ComponentProps } from 'svelte';
  6  
  7  export type TransactionWrapper = {
  8    title: string;
  9    transaction: TransactionLike;
 10    gasless?: {
 11      nonceGetter: () => Promise<number>;
 12      ERC2771Data: (nonce: number) => {
 13        domain: TypedDataDomain;
 14        types: Record<string, TypedDataField[]>;
 15        payload: Record<string, unknown>;
 16      };
 17    };
 18    applyGasBuffer: boolean;
 19  };
 20  
 21  /**
 22   * Use this to display transactions that are handled off-chain (e.g. through Gelato Relay) but have to be awaited
 23   * as part of a multi-transaction interaction.
 24   */
 25  export type ExternalTransaction = {
 26    title: string;
 27    external: true;
 28    expectedDurationMs: number;
 29    expectedDurationText: string;
 30    /** When this resolves, moves on to next transaction */
 31    promise: () => Promise<void>;
 32  };
 33  
 34  export type TransactionWrapperOrExternalTransaction = TransactionWrapper | ExternalTransaction;
 35  
 36  export interface TransactPayload<T> {
 37    before?: T;
 38    transactions: (
 39      context: Context<T>,
 40    ) =>
 41      | TransactionWrapperOrExternalTransaction[]
 42      | Promise<TransactionWrapperOrExternalTransaction[]>;
 43    after?: (receipts: TransactionReceipt[], context: Context<T>) => PromiseLike<void>;
 44    afterSafe?: (
 45      sendTransactionsResponse: SendTransactionsResponse,
 46      context: Context<T>,
 47    ) => PromiseLike<void>;
 48    headline: string;
 49    description?: string;
 50    icon?: {
 51      component: Component<any>;
 52      props?: Record<string, unknown>;
 53    };
 54    messages?: {
 55      duringBefore?: string;
 56      duringAfter?: string;
 57    };
 58  }
 59  
 60  export type SomeTransactPayload = <R>(
 61    payload: <T extends BeforeFunc | undefined>(transactPayload: TransactPayload<T>) => R,
 62  ) => R;
 63  
 64  export type BeforeFunc = () => PromiseLike<Record<string, unknown> | void>;
 65  
 66  type Context<T> = T extends BeforeFunc ? Awaited<ReturnType<T>> : undefined;
 67  
 68  export function makeTransactPayload<T extends BeforeFunc | undefined>(
 69    i: TransactPayload<T>,
 70  ): SomeTransactPayload {
 71    return (cb) => cb(i);
 72  }
 73  
 74  export interface UpdateAwaitStepParams {
 75    message?: string;
 76    subtitle?: string;
 77    link?: {
 78      url: string;
 79      label: string;
 80    };
 81    icon?: {
 82      component: Component;
 83      props: Record<string, unknown>;
 84    };
 85  }
 86  
 87  export type UpdateAwaitStepFn = (params: UpdateAwaitStepParams) => void;
 88  
 89  export interface AwaitPendingPayload extends UpdateAwaitStepParams {
 90    message: string;
 91    /**
 92     * The promise to await. `updateFn` may be used to adjust the icon
 93     * and text displayed on the await step before the promise resolves.
 94     */
 95    promise: (updateFn: UpdateAwaitStepFn) => Promise<unknown>;
 96  }
 97  
 98  export interface MovePayload {
 99    by: number;
100  }
101  
102  export interface SidestepPayload {
103    steps: Steps;
104    onSidestepComplete?: () => void;
105  }
106  
107  export type StepComponentEvents = {
108    /** Go forward one step (or a custom amount by setting `by`). */
109    goForward: MovePayload | void;
110    /** Go backward one step (or a custom amount by setting `by`). */
111    goBackward: MovePayload | void;
112    /**
113     * Await a promise while displaying a customizable spinner dialog.
114     * Once the passed promise is resolved, advances in the flow. If
115     * the promise rejects, displays a rich error message step, with
116     * the ability to jump back to the step that triggered the await.
117     */
118    await: AwaitPendingPayload;
119    /**
120     * Temporarily append a secondary flow after the current step, and
121     * navigate to the first step in the sidestep. Once the sidestep flow
122     * triggers `conclude`, go back to the original step in the original flow.
123     */
124    sidestep: SidestepPayload;
125    /**
126     * Conclude a flow. Either goes back to the original flow if a sidestep
127     * is currently active, or closes the stepper modal.
128     */
129    conclude: void;
130    // TODO: add description.
131    transact: SomeTransactPayload;
132  };
133  
134  type OmitContext<T> = Omit<T, 'context'>;
135  
136  type ComponentAndProps<T extends Component<any>> = {
137    component: T;
138    props: OmitContext<ComponentProps<T>>;
139  };
140  
141  export type Step<T extends Component<any>> = {
142    component: T;
143    props: OmitContext<ComponentProps<T>>;
144    condition?: () => boolean;
145    staticHeaderComponent?: ComponentAndProps<Component>;
146  };
147  
148  type SomeStep = <R>(step: <T extends Component<any>>(step: Step<T>) => R) => R;
149  
150  export type Steps = SomeStep[];
151  
152  export function makeStep<T extends Component<any>>(i: Step<T>): SomeStep {
153    return (cb) => cb(i);
154  }