Skip to content

Commit 2254c56

Browse files
Accept any pending transfer api confirms
1 parent 8009b51 commit 2254c56

File tree

1 file changed

+45
-41
lines changed

1 file changed

+45
-41
lines changed

packages/api/src/routers/temporal.ts

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { throwIf } from 'app/utils/throwIf'
1111
import debug from 'debug'
1212
import type { UserOperation } from 'permissionless'
1313
import { getUserOperationHash } from 'permissionless/utils'
14-
import { withRetry } from 'viem'
14+
import { type Hex, withRetry } from 'viem'
1515
import { z } from 'zod'
1616
import { createTRPCRouter, protectedProcedure } from '../trpc'
1717

@@ -32,6 +32,8 @@ export const temporalRouter = createTRPCRouter({
3232
session: { user },
3333
},
3434
}) => {
35+
const startTime = new Date(Date.now())
36+
3537
const noteValidationError = note
3638
? formFields.note.safeParse(decodeURIComponent(note)).error
3739
: null
@@ -81,47 +83,27 @@ export const temporalRouter = createTRPCRouter({
8183
message: e.message,
8284
})
8385
})
84-
85-
log(`Workflow Created: ${workflowId}`)
86-
8786
await withRetry(
8887
async () => {
89-
const [initialized, receipt] = await Promise.allSettled([
90-
lookupIntializedWorkflow(workflowId)
91-
.then(() => true)
92-
.catch(() => false),
93-
(async () => {
94-
// maybe bundler already has the receipt
95-
const receipt = await baseMainnetBundlerClient.getUserOperationReceipt({
96-
hash: userOpHash,
97-
})
98-
if (!receipt) return null
99-
const supabase = createSupabaseAdminClient()
100-
// do not redirect unless it's been indexed into send_account_transfers
101-
const { data, error } = await supabase
102-
.from('send_account_transfers')
103-
.select('*')
104-
.eq('tx_hash', hexToBytea(receipt.receipt.transactionHash))
105-
.maybeSingle()
106-
throwIf(error)
107-
return data
108-
})(),
109-
] as const)
88+
const initPromise = lookupInitializedWorkflow(workflowId, startTime).catch((e) => {
89+
log(e)
90+
return Promise.reject()
91+
})
11092

111-
if (initialized.status === 'fulfilled' && initialized.value) {
112-
log('transfer initialized')
113-
return true
114-
}
93+
const receiptPromise = lookupTransferReceipt(userOpHash, startTime).catch((e) => {
94+
log(e)
95+
return Promise.reject()
96+
})
11597

116-
if (receipt.status === 'fulfilled' && receipt.value) {
117-
log('userop already onchain', receipt.value.tx_hash)
118-
return true
98+
try {
99+
return await Promise.any([initPromise, receiptPromise])
100+
} catch (error) {
101+
throw new TRPCError({
102+
code: 'PRECONDITION_FAILED',
103+
message:
104+
'Pending transfer not found: Please manually verify whether the send was completed.',
105+
})
119106
}
120-
121-
throw new TRPCError({
122-
code: 'PRECONDITION_FAILED',
123-
message: 'Transfer not yet submitted',
124-
})
125107
},
126108
{
127109
retryCount: 20,
@@ -132,7 +114,7 @@ export const temporalRouter = createTRPCRouter({
132114
},
133115
shouldRetry({ error: e }) {
134116
const error = e as unknown as PostgrestError
135-
if (error.code === 'PGRST116' || error.message === 'Transfer not yet submitted') {
117+
if (error.code === 'PGRST116' || error.code === 'PRECONDITION_FAILED') {
136118
return true // retry on no rows
137119
}
138120
return false
@@ -145,16 +127,38 @@ export const temporalRouter = createTRPCRouter({
145127
),
146128
})
147129

148-
async function lookupIntializedWorkflow(workflowId: string): Promise<{ status: string }> {
149-
// check if the transfer is initialized
130+
async function lookupTransferReceipt(userOpHash: Hex, startTime: Date): Promise<boolean> {
131+
const blockTimeWindow = Math.floor(startTime.getTime() / 1000) - 5 * 60 // subtract 5 minutes as a buffer window
132+
const receipt = await baseMainnetBundlerClient.getUserOperationReceipt({
133+
hash: userOpHash,
134+
})
135+
assert(!!receipt, 'Transfer not onchain')
136+
137+
const supabase = createSupabaseAdminClient()
138+
const { count, error } = await supabase
139+
.from('send_account_transfers')
140+
.select('*', { count: 'exact', head: true })
141+
.eq('tx_hash', hexToBytea(receipt.receipt.transactionHash))
142+
.gte('block_time', blockTimeWindow)
143+
144+
throwIf(error)
145+
assert(count !== null && count > 0, 'Transfer not indexed')
146+
log('userop already onchain', receipt.receipt.transactionHash)
147+
return true
148+
}
149+
150+
async function lookupInitializedWorkflow(workflowId: string, startTime: Date): Promise<boolean> {
150151
const supabaseAdmin = createSupabaseAdminClient()
151152
const { data, error } = await supabaseAdmin
152153
.from('activity')
153154
.select('data->>status')
154155
.eq('event_id', workflowId)
155156
.eq('event_name', 'temporal_send_account_transfers')
157+
.gte('created_at', startTime.toISOString())
156158
.single()
159+
157160
throwIf(error)
158161
assert(!!data && data.status !== 'initialized', 'Transfer not yet submitted')
159-
return data
162+
log(`${data.status} transfer found: ${workflowId}`)
163+
return true
160164
}

0 commit comments

Comments
 (0)