emblem-vault-sdk 2.9.1 → 2.10.0

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.
package/docs/index.html CHANGED
@@ -122,7 +122,8 @@
122
122
  let contract = window.curatedContracts[index];
123
123
  let template = contract.generateCreateTemplate(contract)
124
124
  template.chainId = 1
125
- template.toAddress = document.getElementById('ethAddress').value;
125
+ let toAddr = document.getElementById('toAddress').value;
126
+ template.toAddress = toAddr ? toAddr : document.getElementById('ethAddress').value;
126
127
  template.fromAddress = document.getElementById('ethAddress').value;
127
128
  window.contract = contract;
128
129
  window.template = template;
@@ -204,6 +205,8 @@
204
205
  </script>
205
206
  <input type="text" id="ethAddress" placeholder="Enter ETH Address" oninput="localStorage.setItem('ethAddress', this.value)" value="">
206
207
 
208
+ <input type="text" id="toAddress" placeholder="To Address (optional)" oninput="localStorage.setItem('toAddress', this.value)" value="">
209
+
207
210
  <input type="checkbox" id="hideNotMintableCheckbox" checked onchange="generateDropdown(window.curatedContracts)">
208
211
  <label for="hideNotMintableCheckbox">Hide Not Mintable</label>
209
212
 
@@ -243,6 +246,9 @@
243
246
  <a href="steps.html" target="_blank">Full Create / Mint : Demo</a>
244
247
  </div>
245
248
 
246
- <script>document.getElementById('ethAddress').value = localStorage.getItem('ethAddress');</script>
249
+ <script>
250
+ document.getElementById('ethAddress').value = localStorage.getItem('ethAddress');
251
+ document.getElementById('toAddress').value = localStorage.getItem('toAddress');
252
+ </script>
247
253
  </body>
248
254
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emblem-vault-sdk",
3
- "version": "2.9.1",
3
+ "version": "2.10.0",
4
4
  "description": "Emblem Vault Software Development Kit",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -12,7 +12,7 @@
12
12
  "test:browser": "JEST_ENV=browser jest",
13
13
  "test:all": "npm run test:node && npm run test:browser",
14
14
  "build": "tsc",
15
- "postbuild": "node ./script/version.js && mv dist/index.d.ts types/index.d.ts && mv dist/types.d.ts types/types.d.ts && mv dist/utils.d.ts types/utils.d.ts && mv dist/derive.d.ts types/derive.d.ts",
15
+ "postbuild": "node ./script/version.js && mv dist/index.d.ts types/index.d.ts && mv dist/types.d.ts types/types.d.ts && mv dist/utils.d.ts types/utils.d.ts && mv dist/derive.d.ts types/derive.d.ts && mv dist/constants.d.ts types/constants.d.ts && mv dist/evm-operations.d.ts types/evm-operations.d.ts && mv dist/signing-messages.d.ts types/signing-messages.d.ts && mv dist/vault-utils.d.ts types/vault-utils.d.ts",
16
16
  "bundle": "npm run build && browserify dist/index.js -o dist/bundle.js && cp dist/bundle.js docs/bundle.js",
17
17
  "watch": "onchange 'src/*.ts' -- npm run bundle"
18
18
  },
@@ -24,6 +24,7 @@
24
24
  "@types/crypto-js": "^4.2.2",
25
25
  "@types/jest": "^29.5.11",
26
26
  "browserify": "^17.0.0",
27
+ "esmify": "^2.1.1",
27
28
  "jest": "^29.7.0",
28
29
  "jest-environment-jsdom": "^29.7.0",
29
30
  "jest-puppeteer": "^9.0.2",
@@ -44,6 +45,7 @@
44
45
  "bitcore-mnemonic": "^10.0.23",
45
46
  "crypto-js": "^4.2.0",
46
47
  "ethereumjs-util": "^7.1.5",
48
+ "ethers": "^5.7.2",
47
49
  "install": "^0.13.0",
48
50
  "npm": "^10.4.0",
49
51
  "sats-connect": "^1.4.1",
@@ -54,4 +56,4 @@
54
56
  "engines": {
55
57
  "node": ">=20.5.1"
56
58
  }
57
- }
59
+ }
package/readme.md CHANGED
@@ -8,6 +8,7 @@ The Emblem Vault SDK is a JavaScript library that provides functionality for int
8
8
  - [Fetching Curated Contracts](#fetching-curated-contracts)
9
9
  - [Creating a Vault](#creating-a-vault)
10
10
  - [Refreshing Vault Balance](#refreshing-vault-balance)
11
+ - [Fetching Vaults by Type](#fetching-vaults-by-type)
11
12
  - [Validating Mintability](#validating-mintability)
12
13
  - [Performing a Mint](#performing-a-mint)
13
14
  - [Utility Functions](#utility-functions)
@@ -88,6 +89,37 @@ To refresh the balance of a vault, use the refreshBalance method:
88
89
  ```javascript
89
90
  vaultBalance = await sdk.refreshBalance(vaultData.tokenId, updateLogCallback);
90
91
  ```
92
+
93
+ ## Fetching Vaults by Type
94
+ To fetch vaults owned by an address, use the `fetchVaultsOfType` method:
95
+
96
+ ```javascript
97
+ // Fetch all vaults (returns full array)
98
+ const vaults = await sdk.fetchVaultsOfType('created', '0xYourAddress');
99
+
100
+ // Vault types: "vaulted", "unvaulted", "created"
101
+ ```
102
+
103
+ ### Pagination Support
104
+ For large collections, use pagination to fetch vaults in pages:
105
+
106
+ ```javascript
107
+ // Fetch a specific page (returns { data, pagination } object)
108
+ const result = await sdk.fetchVaultsOfType('created', '0xYourAddress', { page: 1, limit: 100 });
109
+
110
+ console.log(result.data); // Array of vaults for this page
111
+ console.log(result.pagination); // { page: 1, limit: 100, total: 500, totalPages: 5 }
112
+ ```
113
+
114
+ ### Fetching All Vaults with Progress
115
+ To automatically fetch all pages with progress tracking:
116
+
117
+ ```javascript
118
+ const allVaults = await sdk.fetchAllVaultsOfType('created', '0xYourAddress', (page, totalPages, total) => {
119
+ console.log(`Fetched page ${page}/${totalPages} (${total} total vaults)`);
120
+ });
121
+ ```
122
+
91
123
  ## Validating Mintability
92
124
  To validate if a vault is mintable, use the allowed method of the curated contract object:
93
125
 
@@ -0,0 +1,47 @@
1
+ // ============================================================================
2
+ // Supported Chains
3
+ // ============================================================================
4
+
5
+ export const ETHEREUM_MAINNET_CHAIN_ID = 1;
6
+ export const POLYGON_MAINNET_CHAIN_ID = 137;
7
+ export const SOLANA_CHAIN_IDENTIFIER = 'solana';
8
+
9
+ export type ChainType = 'evm' | 'solana';
10
+
11
+ export interface ChainConfig {
12
+ type: ChainType;
13
+ name: string;
14
+ handlerContract?: string;
15
+ unvaultingContract?: string;
16
+ }
17
+
18
+ export const SUPPORTED_CHAINS: Record<number | string, ChainConfig> = {
19
+ [ETHEREUM_MAINNET_CHAIN_ID]: {
20
+ type: 'evm',
21
+ name: 'Ethereum Mainnet',
22
+ handlerContract: '0x23859b51117dbFBcdEf5b757028B18d7759a4460',
23
+ unvaultingContract: '0x214C964bBd3640971E111d3a994CbB89b296a9ad',
24
+ },
25
+ [POLYGON_MAINNET_CHAIN_ID]: {
26
+ type: 'evm',
27
+ name: 'Polygon Mainnet',
28
+ handlerContract: '0x23859b51117dbFBcdEf5b757028B18d7759a4460',
29
+ unvaultingContract: '0x214C964bBd3640971E111d3a994CbB89b296a9ad',
30
+ },
31
+ };
32
+
33
+ // ============================================================================
34
+ // Contract Addresses (default to Ethereum mainnet)
35
+ // ============================================================================
36
+
37
+ export const HANDLER_CONTRACT_ADDRESS = '0x23859b51117dbFBcdEf5b757028B18d7759a4460';
38
+ export const UNVAULTING_DIAMOND_ADDRESS = '0x214C964bBd3640971E111d3a994CbB89b296a9ad';
39
+ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
40
+
41
+ // ============================================================================
42
+ // API Endpoints
43
+ // ============================================================================
44
+
45
+ export const EMBLEM_API_V2 = 'https://v2.emblemvault.io';
46
+ export const EMBLEM_API_2 = 'https://api2.emblemvault.io';
47
+ export const TORUS_SIGNER_API = 'https://tor-us-signer-coval.vercel.app';
@@ -0,0 +1,391 @@
1
+ import type {
2
+ EvmSigner,
3
+ EmblemVaultClient,
4
+ ProgressCallback,
5
+ MintResult,
6
+ ClaimResult,
7
+ RemoteMintSignature,
8
+ RemoteUnvaultSignature,
9
+ MetaData,
10
+ SdkContext,
11
+ } from './types';
12
+
13
+ import {
14
+ ZERO_ADDRESS,
15
+ EMBLEM_API_2,
16
+ TORUS_SIGNER_API,
17
+ } from './constants';
18
+
19
+ import {
20
+ buildMintMessage,
21
+ buildClaimMessage,
22
+ buildUnvaultMessage,
23
+ buildDeleteMessage,
24
+ } from './signing-messages';
25
+
26
+ import {
27
+ getHandlerContractAddress,
28
+ getUnvaultingContractAddress,
29
+ } from './vault-utils';
30
+
31
+ import { fetchData, parseBigIntValue } from './utils';
32
+
33
+ const EVM_RPC_URLS: Record<number, string> = {
34
+ 1: 'https://eth.llamarpc.com',
35
+ 137: 'https://polygon-rpc.com',
36
+ };
37
+
38
+ function getEvmRpcUrl(chainId: number): string {
39
+ return EVM_RPC_URLS[chainId] || EVM_RPC_URLS[1];
40
+ }
41
+
42
+ export async function performMintEvm(
43
+ ctx: SdkContext,
44
+ client: EmblemVaultClient,
45
+ tokenId: string,
46
+ chainId: number,
47
+ callback?: ProgressCallback
48
+ ): Promise<MintResult> {
49
+ callback?.('Initializing EVM signer...');
50
+ const { ethers } = await import('ethers');
51
+ const provider = new ethers.providers.JsonRpcProvider(getEvmRpcUrl(chainId));
52
+ const wallet = await client.toEthersWallet(provider);
53
+ wallet.setChainId?.(chainId);
54
+
55
+ callback?.('Signing mint request...');
56
+ const mintMessage = buildMintMessage(tokenId);
57
+ const mintRequestSig = await wallet.signMessage(mintMessage);
58
+
59
+ callback?.('Getting remote mint signature...');
60
+ const remoteMintSig = await requestRemoteMintSignature(ctx, tokenId, mintRequestSig, chainId);
61
+
62
+ callback?.('Submitting mint transaction...');
63
+ const txHash = await submitMintTransaction(wallet, remoteMintSig, chainId, callback);
64
+
65
+ callback?.('Mint complete!', { txHash });
66
+ return { txHash, tokenId, chainId };
67
+ }
68
+
69
+ export async function performClaimEvm(
70
+ ctx: SdkContext,
71
+ client: EmblemVaultClient,
72
+ tokenId: string,
73
+ chainId: number,
74
+ metadata: MetaData,
75
+ claimIdentifier: string,
76
+ vaultIsV2: boolean,
77
+ needsOnChainUnvault: boolean,
78
+ callback?: ProgressCallback
79
+ ): Promise<ClaimResult> {
80
+ callback?.('Initializing EVM signer...');
81
+ const { ethers } = await import('ethers');
82
+ const provider = new ethers.providers.JsonRpcProvider(getEvmRpcUrl(chainId));
83
+ const wallet = await client.toEthersWallet(provider);
84
+ wallet.setChainId?.(chainId);
85
+
86
+ // Determine if vault needs on-chain claim/unvault before getting keys
87
+ // For minted vaults (live=true, status=unclaimed), we must claim on-chain first
88
+ const isMinted = metadata.live === true || metadata.status === 'unclaimed';
89
+ const isAlreadyClaimed = metadata.status === 'claimed' || Boolean(metadata.claimedBy);
90
+
91
+ if (isMinted && !isAlreadyClaimed) {
92
+ if (needsOnChainUnvault) {
93
+ // V2 vault: use unvaultWithSignedPrice
94
+ await performOnChainUnvault(
95
+ ctx,
96
+ wallet,
97
+ tokenId,
98
+ claimIdentifier,
99
+ metadata.collectionAddress,
100
+ chainId,
101
+ callback
102
+ );
103
+ } else {
104
+ // Non-V2 vault: use handler's claim function
105
+ const targetContractAddress = metadata.targetContract?.[chainId] || metadata.collectionAddress;
106
+ if (targetContractAddress) {
107
+ callback?.('Performing on-chain claim...');
108
+ await performLegacyClaim(wallet, targetContractAddress, claimIdentifier, chainId, callback);
109
+ }
110
+ }
111
+ }
112
+
113
+ callback?.('Signing claim message...');
114
+ const claimMessage = buildClaimMessage(claimIdentifier, vaultIsV2);
115
+ const signature = await wallet.signMessage(claimMessage);
116
+
117
+ callback?.('Requesting claim token...');
118
+ const jwt = await requestClaimToken(tokenId, signature, chainId);
119
+
120
+ callback?.('Requesting remote key...');
121
+ const decryptionKeys = await ctx.requestRemoteKey(tokenId, jwt, callback);
122
+ if (!decryptionKeys) {
123
+ throw new Error('Failed to get decryption key');
124
+ }
125
+
126
+ callback?.('Decrypting vault keys...');
127
+ return await ctx.decryptVaultKeys(tokenId, decryptionKeys, callback);
128
+ }
129
+
130
+ export async function deleteVaultEvm(
131
+ client: EmblemVaultClient,
132
+ tokenId: string,
133
+ chainId: number,
134
+ callback?: ProgressCallback
135
+ ): Promise<boolean> {
136
+ callback?.('Initializing EVM signer...');
137
+ const wallet = await client.toEthersWallet(null);
138
+ wallet.setChainId?.(chainId);
139
+
140
+ callback?.('Signing delete message...');
141
+ const deleteMessage = buildDeleteMessage(tokenId);
142
+ const signature = await wallet.signMessage(deleteMessage);
143
+
144
+ callback?.('Deleting vault...');
145
+ const response = await fetch(`${EMBLEM_API_2}/v2/delete`, {
146
+ method: 'POST',
147
+ headers: {
148
+ 'Content-Type': 'application/json',
149
+ service: 'evmetadata',
150
+ },
151
+ body: JSON.stringify({
152
+ tokenId,
153
+ signature,
154
+ chainId: chainId.toString(),
155
+ }),
156
+ });
157
+
158
+ if (!response.ok) {
159
+ const errorData = await response.json().catch(() => ({}));
160
+ throw new Error(errorData.message || 'Failed to delete vault');
161
+ }
162
+
163
+ callback?.('Vault deleted successfully');
164
+ return true;
165
+ }
166
+
167
+ async function requestRemoteMintSignature(
168
+ ctx: SdkContext,
169
+ tokenId: string,
170
+ signature: string,
171
+ chainId: number
172
+ ): Promise<RemoteMintSignature> {
173
+ const url = `${ctx.baseUrl}/mint-curated`;
174
+ const remoteMintResponse = await fetchData(
175
+ url,
176
+ ctx.apiKey,
177
+ 'POST',
178
+ {
179
+ method: 'buyWithSignedPrice',
180
+ tokenId,
181
+ signature,
182
+ chainId: chainId.toString(),
183
+ }
184
+ );
185
+
186
+ // Handle both 'error' and 'err' fields (API inconsistency)
187
+ const errorMsg = remoteMintResponse.error || remoteMintResponse.err;
188
+ if (errorMsg) {
189
+ const message = typeof errorMsg === 'string'
190
+ ? errorMsg
191
+ : errorMsg.msg || errorMsg.message || JSON.stringify(errorMsg);
192
+ throw new Error(message);
193
+ }
194
+
195
+ return remoteMintResponse as RemoteMintSignature;
196
+ }
197
+
198
+ async function submitMintTransaction(
199
+ wallet: EvmSigner,
200
+ remoteMintSig: RemoteMintSignature,
201
+ chainId: number,
202
+ callback?: ProgressCallback
203
+ ): Promise<string> {
204
+ const { ethers } = await import('ethers');
205
+
206
+ const handlerAddress = getHandlerContractAddress(chainId);
207
+ const priceBigInt = parseBigIntValue(remoteMintSig._price);
208
+
209
+ const iface = new ethers.utils.Interface([
210
+ 'function buyWithSignedPrice(address _nftAddress, address _payment, uint256 _price, address _to, uint256 _tokenId, uint256 _nonce, bytes _signature, bytes serialNumber, uint256 _amount) payable'
211
+ ]);
212
+
213
+ const data = iface.encodeFunctionData('buyWithSignedPrice', [
214
+ remoteMintSig._nftAddress,
215
+ ZERO_ADDRESS,
216
+ priceBigInt,
217
+ remoteMintSig._to,
218
+ parseBigIntValue(remoteMintSig._tokenId),
219
+ parseBigIntValue(remoteMintSig._nonce),
220
+ remoteMintSig._signature,
221
+ remoteMintSig.serialNumber,
222
+ BigInt(1),
223
+ ]);
224
+
225
+ const tx = await wallet.sendTransaction({
226
+ to: handlerAddress,
227
+ data,
228
+ value: priceBigInt,
229
+ });
230
+
231
+ callback?.('Waiting for confirmation...', { txHash: tx.hash });
232
+ await tx.wait();
233
+
234
+ return tx.hash;
235
+ }
236
+
237
+ async function performOnChainUnvault(
238
+ ctx: SdkContext,
239
+ wallet: EvmSigner,
240
+ tokenId: string,
241
+ claimIdentifier: string,
242
+ nftAddress: string | undefined,
243
+ chainId: number,
244
+ callback?: ProgressCallback
245
+ ): Promise<void> {
246
+ callback?.('Signing unvault request...');
247
+ // Sign with tokenId to match server expectation (server verifies "Unvault: " + req.body.tokenId)
248
+ const unvaultMessage = buildUnvaultMessage(tokenId);
249
+ const unvaultSignature = await wallet.signMessage(unvaultMessage);
250
+
251
+ callback?.('Requesting remote unvault signature...');
252
+ const remoteUnvaultSig = await requestRemoteUnvaultSignature(ctx, tokenId, unvaultSignature, chainId);
253
+
254
+ callback?.('Submitting on-chain unvault transaction...');
255
+ await submitUnvaultTransaction(wallet, remoteUnvaultSig, nftAddress, chainId, callback);
256
+
257
+ callback?.('On-chain unvault complete, retrieving keys...');
258
+ }
259
+
260
+ /**
261
+ * Perform legacy claim by calling the handler contract's claim function
262
+ * This is for non-V2 vaults that don't use signed price unvaulting
263
+ */
264
+ async function performLegacyClaim(
265
+ wallet: EvmSigner,
266
+ targetContractAddress: string,
267
+ tokenId: string,
268
+ chainId: number,
269
+ callback?: ProgressCallback
270
+ ): Promise<void> {
271
+ const { ethers } = await import('ethers');
272
+
273
+ // Handler contract ABI for claim function
274
+ const HANDLER_CLAIM_ABI = [
275
+ 'function claim(address _nftAddress, uint256 _tokenId) external'
276
+ ];
277
+
278
+ const handlerAddress = getHandlerContractAddress(chainId);
279
+ const iface = new ethers.utils.Interface(HANDLER_CLAIM_ABI);
280
+
281
+ const data = iface.encodeFunctionData('claim', [
282
+ targetContractAddress,
283
+ BigInt(tokenId),
284
+ ]);
285
+
286
+ callback?.('Submitting claim transaction...');
287
+ const tx = await wallet.sendTransaction({
288
+ to: handlerAddress,
289
+ data,
290
+ });
291
+
292
+ callback?.('Waiting for claim confirmation...', { txHash: tx.hash });
293
+ await tx.wait();
294
+
295
+ callback?.('On-chain claim complete!');
296
+ }
297
+
298
+ async function requestRemoteUnvaultSignature(
299
+ ctx: SdkContext,
300
+ tokenId: string,
301
+ signature: string,
302
+ chainId: number
303
+ ): Promise<RemoteUnvaultSignature> {
304
+ const response = await fetch(`${ctx.baseUrl}/unvault-curated`, {
305
+ method: 'POST',
306
+ headers: { 'Content-Type': 'application/json' },
307
+ body: JSON.stringify({
308
+ method: 'unvaultWithSignedPrice',
309
+ tokenId,
310
+ signature,
311
+ chainId: chainId.toString(),
312
+ }),
313
+ });
314
+
315
+ const remoteUnvaultSig = await response.json() as RemoteUnvaultSignature;
316
+
317
+ if (!remoteUnvaultSig?.success) {
318
+ throw new Error(
319
+ remoteUnvaultSig?.msg || remoteUnvaultSig?.err || 'Failed to get remote unvault signature'
320
+ );
321
+ }
322
+
323
+ return remoteUnvaultSig;
324
+ }
325
+
326
+ async function submitUnvaultTransaction(
327
+ wallet: EvmSigner,
328
+ remoteUnvaultSig: RemoteUnvaultSignature,
329
+ nftAddress: string | undefined,
330
+ chainId: number,
331
+ callback?: ProgressCallback
332
+ ): Promise<string> {
333
+ const { ethers } = await import('ethers');
334
+
335
+ const unvaultingAddress = getUnvaultingContractAddress(chainId);
336
+ const nftAddressToUse = nftAddress || remoteUnvaultSig._nftAddress;
337
+ const tokenId = parseBigIntValue(remoteUnvaultSig._tokenId);
338
+ const nonce = parseBigIntValue(remoteUnvaultSig._nonce);
339
+ const price = parseBigIntValue(remoteUnvaultSig._price);
340
+ const timestamp = parseBigIntValue(remoteUnvaultSig._timestamp);
341
+
342
+ const iface = new ethers.utils.Interface([
343
+ 'function unvaultWithSignedPrice(address _nftAddress, uint256 _tokenId, uint256 _nonce, address _payment, uint256 _price, bytes _signature, uint256 _timestamp) payable'
344
+ ]);
345
+
346
+ const data = iface.encodeFunctionData('unvaultWithSignedPrice', [
347
+ nftAddressToUse,
348
+ tokenId,
349
+ nonce,
350
+ ZERO_ADDRESS,
351
+ price,
352
+ remoteUnvaultSig._signature,
353
+ timestamp,
354
+ ]);
355
+
356
+ const tx = await wallet.sendTransaction({
357
+ to: unvaultingAddress,
358
+ data,
359
+ value: price,
360
+ });
361
+
362
+ callback?.('Waiting for unvault confirmation...', { txHash: tx.hash });
363
+ await tx.wait();
364
+
365
+ return tx.hash;
366
+ }
367
+
368
+ async function requestClaimToken(
369
+ tokenId: string,
370
+ signature: string,
371
+ chainId: number
372
+ ): Promise<{ token: string }> {
373
+ const response = await fetch(`${TORUS_SIGNER_API}/sign`, {
374
+ method: 'POST',
375
+ headers: {
376
+ 'Content-Type': 'application/json',
377
+ chainid: chainId.toString(),
378
+ },
379
+ body: JSON.stringify({ signature, tokenId }),
380
+ });
381
+
382
+ const jwt = await response.json();
383
+
384
+ if (!jwt || jwt.success === false) {
385
+ throw new Error(
386
+ jwt?.debug ? `Claim failed: ${JSON.stringify(jwt.debug)}` : 'Failed to get claim token'
387
+ );
388
+ }
389
+
390
+ return jwt;
391
+ }