-
Notifications
You must be signed in to change notification settings - Fork 587
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
28 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import type { Address, PublicClient, TransactionReceipt } from 'viem'; | ||
import type { Address, PublicClient, TransactionReceipt } from "viem"; | ||
|
||
const storageKey = 'rk-transactions'; | ||
const storageKey = "rk-transactions"; | ||
|
||
type TransactionStatus = 'pending' | 'confirmed' | 'failed'; | ||
type TransactionStatus = "pending" | "confirmed" | "failed"; | ||
|
||
export interface Transaction { | ||
hash: string; | ||
|
@@ -11,48 +11,48 @@ export interface Transaction { | |
confirmations?: number; | ||
} | ||
|
||
export type NewTransaction = Omit<Transaction, 'status'>; | ||
export type NewTransaction = Omit<Transaction, "status">; | ||
|
||
type Data = Record<string, Record<number, Transaction[] | undefined>>; | ||
|
||
function safeParseJsonData(string: string | null): Data { | ||
try { | ||
const value = string ? JSON.parse(string) : {}; | ||
return typeof value === 'object' ? value : {}; | ||
return typeof value === "object" ? value : {}; | ||
} catch { | ||
return {}; | ||
} | ||
} | ||
|
||
function loadData(): Data { | ||
return safeParseJsonData( | ||
typeof localStorage !== 'undefined' | ||
typeof localStorage !== "undefined" | ||
? localStorage.getItem(storageKey) | ||
: null, | ||
: null | ||
); | ||
} | ||
|
||
const transactionHashRegex = /^0x([A-Fa-f0-9]{64})$/; | ||
|
||
function validateTransaction( | ||
transaction: Transaction | NewTransaction, | ||
transaction: Transaction | NewTransaction | ||
): string[] { | ||
const errors: string[] = []; | ||
|
||
if (!transactionHashRegex.test(transaction.hash)) { | ||
errors.push('Invalid transaction hash'); | ||
errors.push("Invalid transaction hash"); | ||
} | ||
|
||
if (typeof transaction.description !== 'string') { | ||
errors.push('Transaction must have a description'); | ||
if (typeof transaction.description !== "string") { | ||
errors.push("Transaction must have a description"); | ||
} | ||
|
||
if ( | ||
typeof transaction.confirmations !== 'undefined' && | ||
typeof transaction.confirmations !== "undefined" && | ||
(!Number.isInteger(transaction.confirmations) || | ||
transaction.confirmations < 1) | ||
) { | ||
errors.push('Transaction confirmations must be a positiver integer'); | ||
errors.push("Transaction confirmations must be a positiver integer"); | ||
} | ||
|
||
return errors; | ||
|
@@ -68,7 +68,7 @@ export function createTransactionStore({ | |
let provider = initialProvider; | ||
const listeners: Set<() => void> = new Set(); | ||
const transactionListeners: Set< | ||
(txStatus: TransactionReceipt['status']) => void | ||
(txStatus: TransactionReceipt["status"]) => void | ||
> = new Set(); | ||
const transactionRequestCache: Map<string, Promise<void>> = new Map(); | ||
|
||
|
@@ -83,17 +83,17 @@ export function createTransactionStore({ | |
function addTransaction( | ||
account: string, | ||
chainId: number, | ||
transaction: NewTransaction, | ||
transaction: NewTransaction | ||
): void { | ||
const errors = validateTransaction(transaction); | ||
|
||
if (errors.length > 0) { | ||
throw new Error(['Unable to add transaction', ...errors].join('\n')); | ||
throw new Error(["Unable to add transaction", ...errors].join("\n")); | ||
} | ||
|
||
updateTransactions(account, chainId, (transactions) => { | ||
return [ | ||
{ ...transaction, status: 'pending' }, | ||
{ ...transaction, status: "pending" }, | ||
...transactions.filter(({ hash }) => { | ||
// Omit any duplicate transactions | ||
return hash !== transaction.hash; | ||
|
@@ -112,22 +112,22 @@ export function createTransactionStore({ | |
account: string, | ||
chainId: number, | ||
hash: string, | ||
status: TransactionStatus, | ||
status: TransactionStatus | ||
): void { | ||
updateTransactions(account, chainId, (transactions) => { | ||
return transactions.map((transaction) => | ||
transaction.hash === hash ? { ...transaction, status } : transaction, | ||
transaction.hash === hash ? { ...transaction, status } : transaction | ||
); | ||
}); | ||
} | ||
|
||
async function waitForPendingTransactions( | ||
account: string, | ||
chainId: number, | ||
chainId: number | ||
): Promise<void> { | ||
await Promise.all( | ||
getTransactions(account, chainId) | ||
.filter((transaction) => transaction.status === 'pending') | ||
.filter((transaction) => transaction.status === "pending") | ||
.map(async (transaction) => { | ||
const { confirmations, hash } = transaction; | ||
const existingRequest = transactionRequestCache.get(hash); | ||
|
@@ -154,7 +154,7 @@ export function createTransactionStore({ | |
chainId, | ||
hash, | ||
// @ts-ignore - types changed with [email protected] | ||
status === 0 || status === 'reverted' ? 'failed' : 'confirmed', | ||
status === 0 || status === "reverted" ? "failed" : "confirmed" | ||
); | ||
|
||
notifyTransactionListeners(status); | ||
|
@@ -163,20 +163,20 @@ export function createTransactionStore({ | |
// If a transaction is not found or cancelled | ||
// viem will throw a 'TransactionNotFoundError'. | ||
// In this case it should mark the transaction as 'failed' | ||
setTransactionStatus(account, chainId, hash, 'failed'); | ||
setTransactionStatus(account, chainId, hash, "failed"); | ||
}); | ||
|
||
transactionRequestCache.set(hash, requestPromise); | ||
|
||
return await requestPromise; | ||
}), | ||
}) | ||
); | ||
} | ||
|
||
function updateTransactions( | ||
account: string, | ||
chainId: number, | ||
updateFn: (transactions: Transaction[]) => Transaction[], | ||
updateFn: (transactions: Transaction[]) => Transaction[] | ||
): void { | ||
// Ensure we’re always operating on the latest data in case we have | ||
// multiple instances/tabs/etc. since we write all data back to | ||
|
@@ -190,7 +190,7 @@ export function createTransactionStore({ | |
const transactions = updateFn(data[account][chainId] ?? []) | ||
// Keep the list of completed transactions from growing indefinitely | ||
.filter(({ status }) => { | ||
return status === 'pending' | ||
return status === "pending" | ||
? true | ||
: completedTransactionCount++ <= MAX_COMPLETED_TRANSACTIONS; | ||
}); | ||
|
@@ -213,7 +213,7 @@ export function createTransactionStore({ | |
} | ||
|
||
function notifyTransactionListeners( | ||
txStatus: TransactionReceipt['status'], | ||
txStatus: TransactionReceipt["status"] | ||
): void { | ||
for (const transactionListener of transactionListeners) { | ||
transactionListener(txStatus); | ||
|
@@ -229,12 +229,11 @@ export function createTransactionStore({ | |
} | ||
|
||
function onTransactionStatus( | ||
fn: (txStatus: TransactionReceipt['status']) => void, | ||
fn: (txStatus: TransactionReceipt["status"]) => void | ||
): () => void { | ||
transactionListeners.add(fn); | ||
|
||
return () => { | ||
console.log('delete!?'); | ||
transactionListeners.delete(fn); | ||
}; | ||
} | ||
|