-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add config for minting backend (#1810)
- Loading branch information
1 parent
73460f8
commit 14d4120
Showing
8 changed files
with
431 additions
and
274 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
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 |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { track } from '@imtbl/metrics'; | ||
|
||
const moduleName = 'minting_backend_sdk'; | ||
|
||
export const trackInitializePersistencePG = () => { | ||
try { | ||
track(moduleName, 'initializePersistencePG'); | ||
} catch { | ||
// ignore | ||
} | ||
}; | ||
|
||
export const trackInitializePersistencePrismaSqlite = () => { | ||
try { | ||
track(moduleName, 'initializePersistencePrismaSqlite'); | ||
} catch { | ||
// ignore | ||
} | ||
}; | ||
|
||
export const trackSubmitMintingRequests = () => { | ||
try { | ||
track(moduleName, 'submitMintingRequests'); | ||
} catch { | ||
// ignore | ||
} | ||
}; | ||
|
||
export const trackProcessMint = () => { | ||
try { | ||
track(moduleName, 'processMint'); | ||
} catch { | ||
// ignore | ||
} | ||
}; | ||
|
||
export const trackRecordMint = () => { | ||
try { | ||
track(moduleName, 'recordMint'); | ||
} catch { | ||
// ignore | ||
} | ||
}; |
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 |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* eslint-disable implicit-arrow-linebreak */ | ||
import { | ||
ImmutableConfiguration, | ||
ModuleConfiguration | ||
} from '@imtbl/config'; | ||
|
||
import { | ||
BlockchainData, | ||
BlockchainDataModuleConfiguration | ||
} from '@imtbl/blockchain-data'; | ||
|
||
import { MintingPersistence } from '../persistence/type'; | ||
|
||
export interface MintingBackendModuleParams { } | ||
|
||
export interface MintingBackendModuleConfiguration | ||
extends ModuleConfiguration<MintingBackendModuleParams> { | ||
persistence: MintingPersistence; | ||
blockchainDataModuleConfiguration: BlockchainDataModuleConfiguration; | ||
} | ||
|
||
export class MintingBackendConfiguration { | ||
readonly baseConfig: ImmutableConfiguration; | ||
|
||
readonly blockChainDataClient: BlockchainData; | ||
|
||
readonly persistence: MintingPersistence; | ||
|
||
constructor({ baseConfig, blockchainDataModuleConfiguration, persistence }: MintingBackendModuleConfiguration) { | ||
this.baseConfig = baseConfig; | ||
this.blockChainDataClient = new BlockchainData(blockchainDataModuleConfiguration); | ||
this.persistence = persistence; | ||
} | ||
} |
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,11 +1,75 @@ | ||
import { ImmutableConfiguration, ModuleConfiguration } from '@imtbl/config'; | ||
import { BlockchainData } from '@imtbl/blockchain-data'; | ||
import { init } from '@imtbl/webhook'; | ||
import { mintingPersistence as mintingPersistencePg } from './persistence/pg/postgres'; | ||
import { mintingPersistence as mintingPersistencePrismaSqlite } from './persistence/prismaSqlite/sqlite'; | ||
import { | ||
submitMintingRequests, processMint, recordMint | ||
submitMintingRequests, processMint, recordMint, | ||
MintRequestEvent | ||
} from './minting'; | ||
import { CreateMintRequest, MintingPersistence } from './persistence/type'; | ||
import { Logger } from './logger/type'; | ||
|
||
export { | ||
submitMintingRequests, processMint, recordMint, | ||
// database clients | ||
mintingPersistencePg, mintingPersistencePrismaSqlite | ||
}; | ||
|
||
export interface MintingBackendModuleConfiguration | ||
extends ModuleConfiguration<undefined> { | ||
persistence: MintingPersistence; | ||
logger?: Logger; | ||
} | ||
|
||
const noopHandlers = { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
zkevmMintRequestUpdated: async (event: MintRequestEvent) => { }, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
others: async (..._args: any) => { } | ||
}; | ||
|
||
export class MintingBackendModule { | ||
private readonly baseConfig: ImmutableConfiguration; | ||
|
||
private readonly persistence: MintingPersistence; | ||
|
||
private readonly blockchainDataClient: BlockchainData; | ||
|
||
private readonly logger: Logger; | ||
|
||
constructor(config: MintingBackendModuleConfiguration) { | ||
this.baseConfig = config.baseConfig; | ||
this.persistence = config.persistence; | ||
this.logger = config.logger || console; | ||
this.blockchainDataClient = new BlockchainData({ | ||
baseConfig: config.baseConfig | ||
}); | ||
} | ||
|
||
async recordMint(mintRequest: CreateMintRequest) { | ||
await recordMint(this.persistence, mintRequest); | ||
} | ||
|
||
async submitMintingRequests(config: { | ||
defaultBatchSize?: number; | ||
chainName?: string; | ||
maxNumberOfTries?: number; | ||
}) { | ||
await submitMintingRequests( | ||
this.persistence, | ||
this.blockchainDataClient, | ||
config | ||
); | ||
} | ||
|
||
async processMint(body: string | Record<string, unknown>, otherHandlers = noopHandlers) { | ||
await init(body, this.baseConfig.environment, { | ||
zkevmMintRequestUpdated: async (event: MintRequestEvent) => { | ||
await processMint(this.persistence, event, this.logger); | ||
otherHandlers.zkevmMintRequestUpdated(event); | ||
}, | ||
others: otherHandlers.others | ||
}); | ||
} | ||
} |
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
182 changes: 93 additions & 89 deletions
182
packages/minting-backend/sdk/src/persistence/pg/postgres.ts
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,94 +1,98 @@ | ||
import type { Pool } from 'pg'; | ||
import { CreateMintRequest, MintingPersistence, SubmittedMintRequest } from '../type'; | ||
import { trackInitializePersistencePG } from '../../analytics'; | ||
|
||
export const mintingPersistence = (client: Pool): MintingPersistence => ({ | ||
recordMint: async (request: CreateMintRequest) => { | ||
const r = await client.query( | ||
` | ||
INSERT INTO im_assets (asset_id, contract_address, owner_address, metadata) | ||
VALUES ($1, $2, $3, $4) ON CONFLICT (asset_id, contract_address) DO NOTHING; | ||
`, | ||
[request.asset_id, request.contract_address, request.owner_address, request.metadata] | ||
); | ||
if (r.rowCount === 0) { | ||
throw new Error('Duplicated mint'); | ||
} | ||
}, | ||
getNextBatchForSubmission: async (limit: number) => { | ||
const res = await client.query(` | ||
UPDATE im_assets SET minting_status = 'submitting' WHERE minting_status IS NULL and id in ( | ||
select id from im_assets where minting_status is null limit $1 for update skip locked | ||
) returning *; | ||
`, [limit]); | ||
return res.rows; | ||
}, | ||
updateMintingStatusToSubmitted: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets SET minting_status = $2 WHERE id = ANY($1); | ||
`, [ids, 'submitted']); | ||
}, | ||
syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => { | ||
// doing a upsert just in case the row has not been created yet | ||
await client.query(` | ||
INSERT INTO im_assets ( | ||
asset_id, | ||
contract_address, | ||
owner_address, | ||
token_id, | ||
minting_status, | ||
metadata_id, | ||
last_imtbl_zkevm_mint_request_updated_id, | ||
error | ||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (asset_id, contract_address) | ||
DO UPDATE SET | ||
owner_address = $3, | ||
token_id = $4, | ||
minting_status = $5, | ||
metadata_id = $6, | ||
last_imtbl_zkevm_mint_request_updated_id = $7, | ||
error = $8 | ||
where ( | ||
im_assets.last_imtbl_zkevm_mint_request_updated_id < $7 OR | ||
im_assets.last_imtbl_zkevm_mint_request_updated_id is null | ||
export const mintingPersistence = (client: Pool): MintingPersistence => { | ||
trackInitializePersistencePG(); | ||
return { | ||
recordMint: async (request: CreateMintRequest) => { | ||
const r = await client.query( | ||
` | ||
INSERT INTO im_assets (asset_id, contract_address, owner_address, metadata) | ||
VALUES ($1, $2, $3, $4) ON CONFLICT (asset_id, contract_address) DO NOTHING; | ||
`, | ||
[request.asset_id, request.contract_address, request.owner_address, request.metadata] | ||
); | ||
`, [ | ||
submittedMintRequest.assetId, | ||
submittedMintRequest.contractAddress, | ||
submittedMintRequest.ownerAddress, | ||
submittedMintRequest.tokenId, | ||
submittedMintRequest.status, | ||
submittedMintRequest.metadataId, | ||
submittedMintRequest.imtblZkevmMintRequestUpdatedId, | ||
submittedMintRequest.error]); | ||
}, | ||
markAsConflict: async (assetIds: string[], contractAddress: string) => { | ||
await client.query(` | ||
UPDATE im_assets | ||
SET minting_status = 'conflicting' | ||
WHERE asset_id = ANY($1) | ||
AND contract_address = $2; | ||
`, [assetIds, contractAddress]); | ||
}, | ||
resetMintingStatus: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets SET minting_status = null WHERE id = ANY($1); | ||
`, [ids]); | ||
}, | ||
markForRetry: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets | ||
SET minting_status = null, tried_count = tried_count + 1 WHERE id = ANY($1); | ||
if (r.rowCount === 0) { | ||
throw new Error('Duplicated mint'); | ||
} | ||
}, | ||
getNextBatchForSubmission: async (limit: number) => { | ||
const res = await client.query(` | ||
UPDATE im_assets SET minting_status = 'submitting' WHERE minting_status IS NULL and id in ( | ||
select id from im_assets where minting_status is null limit $1 for update skip locked | ||
) returning *; | ||
`, [limit]); | ||
return res.rows; | ||
}, | ||
updateMintingStatusToSubmitted: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets SET minting_status = $2 WHERE id = ANY($1); | ||
`, [ids, 'submitted']); | ||
}, | ||
syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => { | ||
// doing a upsert just in case the row has not been created yet | ||
await client.query(` | ||
INSERT INTO im_assets ( | ||
asset_id, | ||
contract_address, | ||
owner_address, | ||
token_id, | ||
minting_status, | ||
metadata_id, | ||
last_imtbl_zkevm_mint_request_updated_id, | ||
error | ||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (asset_id, contract_address) | ||
DO UPDATE SET | ||
owner_address = $3, | ||
token_id = $4, | ||
minting_status = $5, | ||
metadata_id = $6, | ||
last_imtbl_zkevm_mint_request_updated_id = $7, | ||
error = $8 | ||
where ( | ||
im_assets.last_imtbl_zkevm_mint_request_updated_id < $7 OR | ||
im_assets.last_imtbl_zkevm_mint_request_updated_id is null | ||
); | ||
`, [ | ||
submittedMintRequest.assetId, | ||
submittedMintRequest.contractAddress, | ||
submittedMintRequest.ownerAddress, | ||
submittedMintRequest.tokenId, | ||
submittedMintRequest.status, | ||
submittedMintRequest.metadataId, | ||
submittedMintRequest.imtblZkevmMintRequestUpdatedId, | ||
submittedMintRequest.error]); | ||
}, | ||
markAsConflict: async (assetIds: string[], contractAddress: string) => { | ||
await client.query(` | ||
UPDATE im_assets | ||
SET minting_status = 'conflicting' | ||
WHERE asset_id = ANY($1) | ||
AND contract_address = $2; | ||
`, [assetIds, contractAddress]); | ||
}, | ||
resetMintingStatus: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets SET minting_status = null WHERE id = ANY($1); | ||
`, [ids]); | ||
}, | ||
markForRetry: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets | ||
SET minting_status = null, tried_count = tried_count + 1 WHERE id = ANY($1); | ||
`, [ids]); | ||
}, | ||
updateMintingStatusToSubmissionFailed: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets SET minting_status = 'submission_failed' WHERE id = ANY($1); | ||
`, [ids]); | ||
}, | ||
updateMintingStatusToSubmissionFailed: async (ids: string[]) => { | ||
await client.query(` | ||
UPDATE im_assets SET minting_status = 'submission_failed' WHERE id = ANY($1); | ||
`, [ids]); | ||
}, | ||
getMintingRequest: async (contractAddress: string, referenceId: string) => { | ||
const res = await client.query(` | ||
SELECT * FROM im_assets WHERE contract_address = $1 and asset_id = $2; | ||
`, [contractAddress, referenceId]); | ||
return res.rows[0] || null; | ||
} | ||
}); | ||
}, | ||
getMintingRequest: async (contractAddress: string, referenceId: string) => { | ||
const res = await client.query(` | ||
SELECT * FROM im_assets WHERE contract_address = $1 and asset_id = $2; | ||
`, [contractAddress, referenceId]); | ||
return res.rows[0] || null; | ||
} | ||
}; | ||
}; |
Oops, something went wrong.