diff --git a/api/src/feat/transfers/insert-transfer-approval.edgeql b/api/src/feat/transfers/insert-transfer-approval.edgeql new file mode 100644 index 000000000..c755f9544 --- /dev/null +++ b/api/src/feat/transfers/insert-transfer-approval.edgeql @@ -0,0 +1,26 @@ +with accountAddress := $account, + localAccount := as_address(accountAddress), + account := (select Account filter .address = accountAddress), + resultId := $result, + result := assert_single((select Result filter .id = resultId and .transaction.account = account) if exists resultId else {}), + from :=
$from, + to :=
$to, + transfer := ( + insert TransferApproval { + account := account, + systxHash := $systxHash, + result := result, + block := $block, + logIndex := $logIndex, + timestamp := $timestamp, + confirmed := (result is Confirmed ?? true), + from := from, + to := to, + tokenAddress := $token, + amount := $amount, + incoming := (to = localAccount), + outgoing := (from = localAccount), + isFeeTransfer := ((result.transaction.paymaster in {from, to}) ?? false) + } unless conflict + ) +select transfer; \ No newline at end of file diff --git a/api/src/feat/transfers/insert-transfer-approval.query.ts b/api/src/feat/transfers/insert-transfer-approval.query.ts new file mode 100644 index 000000000..c1c7ec21a --- /dev/null +++ b/api/src/feat/transfers/insert-transfer-approval.query.ts @@ -0,0 +1,51 @@ +// GENERATED by @edgedb/generate v0.5.4 + +import type {Executor} from "edgedb"; + +export type InsertTransferApprovalArgs = { + readonly "account": string; + readonly "result"?: string | null; + readonly "from": string; + readonly "to": string; + readonly "systxHash"?: string | null; + readonly "block": bigint; + readonly "logIndex": number; + readonly "timestamp": Date; + readonly "token": string; + readonly "amount": string; +}; + +export type InsertTransferApprovalReturns = { + "id": string; +} | null; + +export function insertTransferApproval(client: Executor, args: InsertTransferApprovalArgs): Promise { + return client.querySingle(`\ +with accountAddress := $account, + localAccount := as_address(accountAddress), + account := (select Account filter .address = accountAddress), + resultId := $result, + result := assert_single((select Result filter .id = resultId and .transaction.account = account) if exists resultId else {}), + from :=
$from, + to :=
$to, + transfer := ( + insert TransferApproval { + account := account, + systxHash := $systxHash, + result := result, + block := $block, + logIndex := $logIndex, + timestamp := $timestamp, + confirmed := (result is Confirmed ?? true), + from := from, + to := to, + tokenAddress := $token, + amount := $amount, + incoming := (to = localAccount), + outgoing := (from = localAccount), + isFeeTransfer := ((result.transaction.paymaster in {from, to}) ?? false) + } unless conflict + ) +select transfer;`, args); + +} diff --git a/api/src/feat/transfers/insert-transfer.edgeql b/api/src/feat/transfers/insert-transfer.edgeql new file mode 100644 index 000000000..57829cc64 --- /dev/null +++ b/api/src/feat/transfers/insert-transfer.edgeql @@ -0,0 +1,31 @@ +with accountAddress := $account, + localAccount := as_address(accountAddress), + account := (select Account filter .address = accountAddress), + resultId := $result, + result := assert_single((select Result filter .id = resultId and .transaction.account = account) if exists resultId else {}), + from :=
$from, + to :=
$to, + transfer := ( + insert Transfer { + account := account, + systxHash := $systxHash, + result := result, + block := $block, + logIndex := $logIndex, + timestamp := $timestamp, + confirmed := (result is Confirmed ?? true), + from := from, + to := to, + tokenAddress := $token, + amount := $amount, + incoming := (to = localAccount), + outgoing := (from = localAccount), + isFeeTransfer := ((result.transaction.paymaster in {from, to}) ?? false) + } unless conflict + ) +select transfer { + id, + internal, + isFeeTransfer, + accountUsers := .account.approvers.user.id +} \ No newline at end of file diff --git a/api/src/feat/transfers/insert-transfer.query.ts b/api/src/feat/transfers/insert-transfer.query.ts new file mode 100644 index 000000000..349906e30 --- /dev/null +++ b/api/src/feat/transfers/insert-transfer.query.ts @@ -0,0 +1,59 @@ +// GENERATED by @edgedb/generate v0.5.4 + +import type {Executor} from "edgedb"; + +export type InsertTransferArgs = { + readonly "account": string; + readonly "result"?: string | null; + readonly "from": string; + readonly "to": string; + readonly "systxHash"?: string | null; + readonly "block": bigint; + readonly "logIndex": number; + readonly "timestamp": Date; + readonly "token": string; + readonly "amount": string; +}; + +export type InsertTransferReturns = { + "id": string; + "internal": boolean; + "isFeeTransfer": boolean; + "accountUsers": Array; +} | null; + +export function insertTransfer(client: Executor, args: InsertTransferArgs): Promise { + return client.querySingle(`\ +with accountAddress := $account, + localAccount := as_address(accountAddress), + account := (select Account filter .address = accountAddress), + resultId := $result, + result := assert_single((select Result filter .id = resultId and .transaction.account = account) if exists resultId else {}), + from :=
$from, + to :=
$to, + transfer := ( + insert Transfer { + account := account, + systxHash := $systxHash, + result := result, + block := $block, + logIndex := $logIndex, + timestamp := $timestamp, + confirmed := (result is Confirmed ?? true), + from := from, + to := to, + tokenAddress := $token, + amount := $amount, + incoming := (to = localAccount), + outgoing := (from = localAccount), + isFeeTransfer := ((result.transaction.paymaster in {from, to}) ?? false) + } unless conflict + ) +select transfer { + id, + internal, + isFeeTransfer, + accountUsers := .account.approvers.user.id +}`, args); + +} diff --git a/api/src/feat/transfers/transfers.events.ts b/api/src/feat/transfers/transfers.events.ts index 0d4ec650a..a9024dd7d 100644 --- a/api/src/feat/transfers/transfers.events.ts +++ b/api/src/feat/transfers/transfers.events.ts @@ -2,9 +2,8 @@ import { Injectable, Logger } from '@nestjs/common'; import { Address, UAddress, asAddress, asUAddress, isTruthy, ETH_ADDRESS, isEthToken } from 'lib'; import { ERC20 } from 'lib/dapps'; import { EventsService, OptimisticEvent, ConfirmedEvent } from '../events/events.service'; -import { and, DatabaseService } from '~/core/database'; +import { DatabaseService } from '~/core/database'; import e from '~/edgeql-js'; -import { selectAccount } from '../accounts/accounts.util'; import { uuid } from 'edgedb/dist/codecs/ifaces'; import { EventPayload, PubsubService } from '~/core/pubsub/pubsub.service'; import { getAbiItem } from 'viem'; @@ -14,6 +13,8 @@ import { BalancesService } from '~/core/balances/balances.service'; import Decimal from 'decimal.js'; import { TokensService } from '~/feat/tokens/tokens.service'; import { ampli } from '~/util/ampli'; +import { insertTransfer } from './insert-transfer.query'; +import { insertTransferApproval } from './insert-transfer-approval.query'; export const transferTrigger = (account: UAddress) => `transfer.account.${account}`; export interface TransferSubscriptionPayload extends EventPayload<'transfer'> { @@ -66,55 +67,19 @@ export class TransfersEvents { await Promise.all( accounts.map(async (account) => { - const selectedAccount = selectAccount(account); - const result = event.result - ? e.assert_single( - e.select(e.Result, (r) => ({ - filter: and( - e.op(r.id, '=', e.uuid(event.result!)), - e.op(r.transaction.account, '=', selectedAccount), - ), - })), - ) - : e.cast(e.Result, e.set()); - - const transfer = await this.db.query( - e.select( - e - .insert(e.Transfer, { - account: selectedAccount, - systxHash: log.transactionHash, - result, - logIndex: event.logIndex, - block: event.block, - timestamp: event.timestamp, - confirmed: e.op(e.op('exists', result.is(e.Confirmed)), '??', true), - from: localFrom, - to: localTo, - tokenAddress: token, - amount: - from === to - ? '0' - : to === account - ? amount.toString() - : amount.negated().toString(), - incoming: account === to, - outgoing: account === from, - isFeeTransfer: e.op( - e.op(result.transaction.paymaster, 'in', e.set(localFrom, localTo)), - '??', - false, - ), - }) - .unlessConflict(), - (t) => ({ - id: true, - internal: true, - isFeeTransfer: true, - accountUsers: t.account.approvers.user.id, - }), - ), - ); + const transfer = await this.db.exec(insertTransfer, { + account, + systxHash: log.transactionHash, + result: event.result, + block: event.block, + logIndex: event.logIndex, + timestamp: event.timestamp, + from: localFrom, + to: localTo, + token, + amount: + from === to ? '0' : to === account ? amount.toString() : amount.negated().toString(), + }); if (!transfer) return; // Already processed this.balances.invalidate({ account, token }); @@ -167,50 +132,20 @@ export class TransfersEvents { await Promise.all( accounts.map(async (account) => { - const selectedAccount = selectAccount(account); - const result = event.result - ? e.assert_single( - e.select(e.Result, (r) => ({ - filter: and( - e.op(r.id, '=', e.uuid(event.result!)), - e.op(r.transaction.account, '=', selectedAccount), - ), - })), - ) - : e.cast(e.Result, e.set()); - - const approval = await this.db.query( - e - .insert(e.TransferApproval, { - account: selectedAccount, - systxHash: log.transactionHash, - result, - logIndex: event.logIndex, - block: event.block, - timestamp: event.timestamp, - confirmed: e.op(e.op('exists', result.is(e.Confirmed)), '??', true), - from: localFrom, - to: localTo, - tokenAddress: token, - amount: - from === to - ? '0' - : to === account - ? amount.toString() - : amount.negated().toString(), - incoming: account === to, - outgoing: account === from, - isFeeTransfer: e.op( - e.op(result.transaction.paymaster, 'in', e.set(localFrom, localTo)), - '??', - false, - ), - }) - .unlessConflict((t) => ({ - on: e.tuple([t.account, t.block, t.logIndex]), - })), - ); - if (!approval) return; // Already processed + const transfer = await this.db.exec(insertTransferApproval, { + account, + systxHash: log.transactionHash, + result: event.result, + block: event.block, + logIndex: event.logIndex, + timestamp: event.timestamp, + from: localFrom, + to: localTo, + token, + amount: + from === to ? '0' : to === account ? amount.toString() : amount.negated().toString(), + }); + if (!transfer) return; // Already processed if (to === account) { this.log.debug(`Transfer approval ${token}: ${from} -> ${to}`);