@imtbl/sdk 1.33.0 → 1.34.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,260 @@
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable global-require */
3
+ let optionalPackage = null;
4
+ let pgClient = null;
5
+ try {
6
+ optionalPackage = require('pg');
7
+ pgClient = new optionalPackage.Pool({
8
+ user: process.env.PG_USER || 'postgres',
9
+ host: process.env.PG_HOST || 'localhost',
10
+ database: process.env.PG_DB_NAME || 'nullus',
11
+ password: process.env.PG_PASSWORD || 'postgres',
12
+ port: parseInt(process.env.PG_PORT || '5432', 10) || 5432,
13
+ });
14
+ }
15
+ catch (error) {
16
+ console.error('pg is not installed. Please install it by running `npm install pg`');
17
+ optionalPackage = null;
18
+ }
19
+ const client = pgClient;
20
+
21
+ const mintingPersistence = {
22
+ recordMint: async (request) => {
23
+ const r = await client.query(`
24
+ INSERT INTO im_assets (asset_id, contract_address, owner_address, metadata)
25
+ VALUES ($1, $2, $3, $4) ON CONFLICT (asset_id) DO NOTHING;
26
+ `, [request.asset_id, request.contract_address, request.owner_address, request.metadata]);
27
+ if (r.rowCount === 0) {
28
+ throw new Error('Duplicated mint');
29
+ }
30
+ },
31
+ getNextBatchForSubmission: async (limit) => {
32
+ const res = await client.query(`
33
+ UPDATE im_assets SET minting_status = 'submitting' WHERE minting_status IS NULL and id in (
34
+ select id from im_assets where minting_status is null limit $1 for update skip locked
35
+ ) returning *;
36
+ `, [limit]);
37
+ return res.rows;
38
+ },
39
+ updateMintingStatusToSubmitted: async (ids) => {
40
+ await client.query(`
41
+ UPDATE im_assets SET minting_status = $2 WHERE id = ANY($1);
42
+ `, [ids, 'submitted']);
43
+ },
44
+ syncMintingStatus: async (submittedMintRequest) => {
45
+ // doing a upsert just in case the row has not been created yet
46
+ await client.query(`
47
+ INSERT INTO im_assets (
48
+ asset_id,
49
+ contract_address,
50
+ owner_address,
51
+ token_id,
52
+ minting_status,
53
+ metadata_id,
54
+ last_imtbl_zkevm_mint_request_updated_id,
55
+ error
56
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (asset_id, contract_address)
57
+ DO UPDATE SET
58
+ owner_address = $3,
59
+ token_id = $4,
60
+ minting_status = $5,
61
+ metadata_id = $6,
62
+ last_imtbl_zkevm_mint_request_updated_id = $7,
63
+ error = $8
64
+ where (
65
+ im_assets.last_imtbl_zkevm_mint_request_updated_id < $7 OR
66
+ im_assets.last_imtbl_zkevm_mint_request_updated_id is null
67
+ );
68
+ `, [
69
+ submittedMintRequest.assetId,
70
+ submittedMintRequest.contractAddress,
71
+ submittedMintRequest.ownerAddress,
72
+ submittedMintRequest.tokenId,
73
+ submittedMintRequest.status,
74
+ submittedMintRequest.metadataId,
75
+ submittedMintRequest.imtblZkevmMintRequestUpdatedId,
76
+ submittedMintRequest.error
77
+ ]);
78
+ },
79
+ markAsConflict: async (assetIds, contractAddress) => {
80
+ await client.query(`
81
+ UPDATE im_assets
82
+ SET minting_status = 'conflicting'
83
+ WHERE asset_id = ANY($1)
84
+ AND contract_address = $2;
85
+ `, [assetIds, contractAddress]);
86
+ },
87
+ resetMintingStatus: async (ids) => {
88
+ await client.query(`
89
+ UPDATE im_assets SET minting_status = null WHERE id = ANY($1) and contract_address = $2;
90
+ `, [ids]);
91
+ },
92
+ markForRetry: async (ids) => {
93
+ await client.query(`
94
+ UPDATE im_assets
95
+ SET minting_status = null, tried_count = tried_count + 1 WHERE id = ANY($1);
96
+ `, [ids]);
97
+ },
98
+ updateMintingStatusToSubmissionFailed: async (ids) => {
99
+ await client.query(`
100
+ UPDATE im_assets SET minting_status = 'submission_failed' WHERE id = ANY($1);
101
+ `, [ids]);
102
+ },
103
+ getMintingRequest: async (contractAddress, referenceId) => {
104
+ const res = await client.query(`
105
+ SELECT * FROM im_assets WHERE contract_address = $1 and asset_id = $2;
106
+ `, [contractAddress, referenceId]);
107
+ return res.rows[0];
108
+ }
109
+ };
110
+
111
+ // TODO: expose metrics
112
+ // - submitting status count, conflicting status count
113
+ // - failed events count
114
+ const recordMint = async (mintingPersistence, mintRequest) => {
115
+ mintingPersistence.recordMint(mintRequest);
116
+ };
117
+ const defaultMintingDelay = 1000;
118
+ // This function can be called in different node process and will run in parallel to mint assets.
119
+ // Each process will NOT mint the same asset.
120
+ // Monitor your database to see if parallel processes are saturating the database CPU and triggering excessive vacuuming.
121
+ // default batch size will be treated as 100 when it exceeds 100 because minting API has a limit of 100 assets per request.
122
+ const submitMintingRequests = async (mintingPersistence, blockchainDataSDKClient, { defaultBatchSize = 1000, chainName = 'imtbl-zkevm-testnet', maxNumberOfTries = 3, }, logger = console) => {
123
+ let mintingResponse;
124
+ // eslint-disable-next-line no-constant-condition
125
+ while (true) {
126
+ await new Promise((resolve) => {
127
+ setTimeout(resolve, defaultMintingDelay);
128
+ });
129
+ let batchSize = Math.min(mintingResponse
130
+ ? parseInt(mintingResponse.imx_remaining_mint_requests, 10)
131
+ : defaultBatchSize, defaultBatchSize);
132
+ if (batchSize === 0
133
+ && mintingResponse
134
+ && new Date(mintingResponse.imx_mint_requests_limit_reset) > new Date()) {
135
+ logger.info(`minting limit reached, waiting for reset at ${mintingResponse?.imx_mint_requests_limit_reset}`);
136
+ continue;
137
+ }
138
+ if (batchSize === 0) {
139
+ logger.info(`minting limit has been reset, use default batch size: ${defaultBatchSize}`);
140
+ mintingResponse = undefined;
141
+ batchSize = defaultBatchSize;
142
+ }
143
+ // get mints to be submitted
144
+ const pendingMints = await mintingPersistence.getNextBatchForSubmission(batchSize);
145
+ if (pendingMints.length === 0) {
146
+ logger.info('no assets to mint');
147
+ continue;
148
+ }
149
+ // chunk assets by every 100 assets. every chunk should all be for the same contract address.
150
+ const chunkedAssets = pendingMints.sort((a, b) => (a.contract_address > b.contract_address ? 1 : -1)).reduce((acc, row) => {
151
+ if (acc.length === 0) {
152
+ return [{ contractAddress: row.contract_address, assets: [row] }];
153
+ }
154
+ const lastBatch = acc[acc.length - 1];
155
+ if (lastBatch.contractAddress === row.contract_address && lastBatch.assets.length < 100) {
156
+ return [...acc.slice(0, -1), { ...lastBatch, assets: [...lastBatch.assets, row] }];
157
+ }
158
+ return [...acc, { contractAddress: row.contract_address, assets: [row] }];
159
+ }, []);
160
+ // submit minting request for each contract address in parallel
161
+ const mintingResults = await Promise.allSettled(chunkedAssets.map(async ({ contractAddress, assets }) => {
162
+ const mintingRequest = {
163
+ chainName,
164
+ contractAddress,
165
+ createMintRequestRequest: {
166
+ assets: assets.map((row) => ({
167
+ reference_id: row.asset_id,
168
+ owner_address: row.owner_address,
169
+ metadata: row.metadata,
170
+ })),
171
+ },
172
+ };
173
+ try {
174
+ const response = await blockchainDataSDKClient.createMintRequest(mintingRequest);
175
+ logger.info(`mintingResponse: ${JSON.stringify(response, null, 2)}`);
176
+ // update minting status to submitted
177
+ await mintingPersistence.updateMintingStatusToSubmitted(assets.map(({ id }) => id));
178
+ return response;
179
+ }
180
+ catch (e) {
181
+ logger.error(e);
182
+ if (e.code === 'CONFLICT_ERROR'
183
+ && e.details?.id === 'reference_id') {
184
+ try {
185
+ // mark the assets as conflicting, so that we don't send them again.
186
+ await mintingPersistence.markAsConflict(e.details.values, contractAddress);
187
+ // remove non conflicting assets status so that they can be retried.
188
+ await mintingPersistence.resetMintingStatus(assets
189
+ .map(({ id }) => id)
190
+ .filter((id) => !e.details.values.includes(id)));
191
+ }
192
+ catch (e2) {
193
+ logger.error(e2);
194
+ }
195
+ }
196
+ else {
197
+ // separate assets into "need to be retied" and "exceeded max number of tries."
198
+ const { assetsToRetry, assetsExceededMaxNumberOfTries } = assets.reduce((acc, { tried_count = 0, id }) => {
199
+ if (tried_count < maxNumberOfTries) {
200
+ acc.assetsToRetry.push(id);
201
+ }
202
+ else {
203
+ acc.assetsExceededMaxNumberOfTries.push(id);
204
+ }
205
+ return acc;
206
+ }, {
207
+ assetsToRetry: [],
208
+ assetsExceededMaxNumberOfTries: [],
209
+ });
210
+ // remove all assets status so that they can be retried.
211
+ await mintingPersistence.markForRetry(assetsToRetry);
212
+ // mark assets that have exceeded the max number of tries as submission_failed
213
+ await mintingPersistence.updateMintingStatusToSubmissionFailed(assetsExceededMaxNumberOfTries);
214
+ }
215
+ return e;
216
+ }
217
+ }));
218
+ mintingResponse = mintingResults
219
+ .reverse()
220
+ .find((r) => r.status === 'fulfilled')?.value;
221
+ }
222
+ };
223
+ const processMint = async (mintingPersistence, event, logger = console) => {
224
+ if (event.event_name !== 'imtbl_zkevm_mint_request_updated') {
225
+ logger.info(`${event.event_name} is not imtbl_zkevm_mint_request_updated, skip.`);
226
+ return;
227
+ }
228
+ const referenceId = event.data.reference_id;
229
+ if (!referenceId) {
230
+ throw new Error('reference_id not found in webhook event');
231
+ }
232
+ const contractAddress = event.data.contract_address;
233
+ if (!contractAddress) {
234
+ throw new Error('contract_address not found in webhook event');
235
+ }
236
+ if (event.data.status === 'failed') {
237
+ logger.error(`mint failed: ${JSON.stringify(event.data, null, 2)}`);
238
+ }
239
+ const mintReq = await mintingPersistence.getMintingRequest(contractAddress, referenceId);
240
+ if (!mintReq) {
241
+ logger.info(`minting request not found in the database, ${JSON.stringify(event.data)}`);
242
+ }
243
+ const ownerAddress = mintReq?.wallet_address || event.data.owner_address;
244
+ if (!ownerAddress) {
245
+ logger.error('owner_address missing');
246
+ throw new Error('owner_address missing');
247
+ }
248
+ await mintingPersistence.syncMintingStatus({
249
+ tokenId: event.data.token_id,
250
+ status: event.data.status,
251
+ assetId: referenceId,
252
+ contractAddress,
253
+ ownerAddress,
254
+ metadataId: event.data.metadata_id,
255
+ imtblZkevmMintRequestUpdatedId: event.event_id,
256
+ error: event.data.error ? JSON.stringify(event.data.error) : null,
257
+ });
258
+ };
259
+
260
+ export { mintingPersistence as mintingPersistencePg, processMint, recordMint, submitMintingRequests };
package/dist/orderbook.js CHANGED
@@ -132,7 +132,7 @@ const flattenProperties = (properties) => {
132
132
  };
133
133
 
134
134
  // WARNING: DO NOT CHANGE THE STRING BELOW. IT GETS REPLACED AT BUILD TIME.
135
- const SDK_VERSION = '1.33.0';
135
+ const SDK_VERSION = '1.34.0-alpha';
136
136
  const getFrameParentDomain = () => {
137
137
  if (isNode()) {
138
138
  return '';
package/dist/passport.js CHANGED
@@ -11459,7 +11459,7 @@ class MultiRollupApiClients {
11459
11459
  }
11460
11460
 
11461
11461
  // eslint-disable-next-line @typescript-eslint/naming-convention
11462
- const defaultHeaders$1 = { 'x-sdk-version': 'ts-immutable-sdk-1.33.0' };
11462
+ const defaultHeaders$1 = { 'x-sdk-version': 'ts-immutable-sdk-1.34.0-alpha' };
11463
11463
  const createConfig$1 = ({ basePath, headers, }) => {
11464
11464
  if (!basePath.trim()) {
11465
11465
  throw Error('basePath can not be empty');
@@ -11638,7 +11638,7 @@ const flattenProperties = (properties) => {
11638
11638
  };
11639
11639
 
11640
11640
  // WARNING: DO NOT CHANGE THE STRING BELOW. IT GETS REPLACED AT BUILD TIME.
11641
- const SDK_VERSION = '1.33.0';
11641
+ const SDK_VERSION = '1.34.0-alpha';
11642
11642
  const getFrameParentDomain = () => {
11643
11643
  if (isNode()) {
11644
11644
  return '';
@@ -12028,7 +12028,7 @@ const addKeysToHeadersOverride = (baseConfig, overrides) => {
12028
12028
  /* eslint-disable @typescript-eslint/naming-convention */
12029
12029
  class ApiConfiguration extends index$2.Configuration {
12030
12030
  }
12031
- const defaultHeaders = { 'x-sdk-version': 'ts-immutable-sdk-1.33.0' };
12031
+ const defaultHeaders = { 'x-sdk-version': 'ts-immutable-sdk-1.34.0-alpha' };
12032
12032
  /**
12033
12033
  * @dev use createImmutableXConfiguration instead
12034
12034
  */
@@ -12067,7 +12067,7 @@ const createImmutableXConfiguration = ({ basePath, chainID, coreContractAddress,
12067
12067
  chainID,
12068
12068
  coreContractAddress,
12069
12069
  registrationContractAddress,
12070
- sdkVersion: 'ts-immutable-sdk-1.33.0',
12070
+ sdkVersion: 'ts-immutable-sdk-1.34.0-alpha',
12071
12071
  baseConfig,
12072
12072
  });
12073
12073
  const production = ({ baseConfig }) => createImmutableXConfiguration({
@@ -0,0 +1,11 @@
1
+ declare enum Environment {
2
+ PRODUCTION = "production",
3
+ SANDBOX = "sandbox"
4
+ }
5
+
6
+ declare const init: (body: string | Record<string, unknown>, env: Environment, handlers: {
7
+ zkevmMintRequestUpdated: (event: any) => Promise<void>;
8
+ others?: ((event: any) => Promise<void>) | undefined;
9
+ }) => Promise<void>;
10
+
11
+ export { init };