emblem-vault-sdk 2.11.0 → 2.13.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.
@@ -105,7 +105,7 @@ export async function performClaimEvm(
105
105
  const targetContractAddress = metadata.targetContract?.[chainId] || metadata.collectionAddress;
106
106
  if (targetContractAddress) {
107
107
  callback?.('Performing on-chain claim...');
108
- await performLegacyClaim(wallet, targetContractAddress, claimIdentifier, chainId, callback);
108
+ await performLegacyClaim(ctx, wallet, targetContractAddress, claimIdentifier, chainId, callback);
109
109
  }
110
110
  }
111
111
  }
@@ -262,6 +262,7 @@ async function performOnChainUnvault(
262
262
  * This is for non-V2 vaults that don't use signed price unvaulting
263
263
  */
264
264
  async function performLegacyClaim(
265
+ ctx: SdkContext,
265
266
  wallet: EvmSigner,
266
267
  targetContractAddress: string,
267
268
  tokenId: string,
@@ -269,32 +270,141 @@ async function performLegacyClaim(
269
270
  callback?: ProgressCallback
270
271
  ): Promise<void> {
271
272
  const { ethers } = await import('ethers');
272
-
273
- // Handler contract ABI for claim function
273
+
274
+ // The handler's free claim(address,uint256) was retired; claiming requires a
275
+ // witness-signed price via claimWithSignedPrice. Sign "Claim: <tokenId>", get
276
+ // the server-signed price for this asset (supply tier), then call the contract
277
+ // with the server-derived on-chain tokenId + payment.
278
+ callback?.('Signing claim authorization...');
279
+ const claimSig = await wallet.signMessage(`Claim: ${tokenId}`);
280
+
281
+ callback?.('Requesting claim price...');
282
+ const remote = await requestRemoteClaimSignature(ctx, tokenId, claimSig, chainId);
283
+
274
284
  const HANDLER_CLAIM_ABI = [
275
- 'function claim(address _nftAddress, uint256 _tokenId) external'
285
+ 'function claimWithSignedPrice(address _nftAddress, uint256 tokenId, address _payment, uint256 _price, uint256 _nonce, bytes _signature) external payable'
276
286
  ];
277
-
278
287
  const handlerAddress = getHandlerContractAddress(chainId);
279
288
  const iface = new ethers.utils.Interface(HANDLER_CLAIM_ABI);
280
-
281
- const data = iface.encodeFunctionData('claim', [
282
- targetContractAddress,
283
- BigInt(tokenId),
289
+
290
+ const price = ethers.BigNumber.from(remote._price);
291
+ const isEth = remote._payment === '0x0000000000000000000000000000000000000000';
292
+
293
+ const data = iface.encodeFunctionData('claimWithSignedPrice', [
294
+ remote._nftAddress,
295
+ ethers.BigNumber.from(remote._tokenId),
296
+ remote._payment,
297
+ price,
298
+ ethers.BigNumber.from(remote._nonce),
299
+ remote._signature,
284
300
  ]);
285
301
 
286
302
  callback?.('Submitting claim transaction...');
287
303
  const tx = await wallet.sendTransaction({
288
304
  to: handlerAddress,
289
305
  data,
306
+ value: isEth ? price : ethers.constants.Zero,
290
307
  });
291
308
 
292
309
  callback?.('Waiting for claim confirmation...', { txHash: tx.hash });
293
310
  await tx.wait();
294
-
311
+
295
312
  callback?.('On-chain claim complete!');
296
313
  }
297
314
 
315
+ // Witness signature + supply-tier price for a legacy (non-diamond) claim.
316
+ async function requestRemoteClaimSignature(
317
+ ctx: SdkContext,
318
+ tokenId: string,
319
+ signature: string,
320
+ chainId: number
321
+ ): Promise<any> {
322
+ const response = await fetch(`${ctx.baseUrl}/claim-curated`, {
323
+ method: 'POST',
324
+ headers: { 'Content-Type': 'application/json' },
325
+ body: JSON.stringify({ tokenId, signature, chainId: chainId.toString() }),
326
+ });
327
+ const remote = await response.json();
328
+ if (!remote?.success) {
329
+ throw new Error(remote?.msg || remote?.error || 'Failed to get claim signature');
330
+ }
331
+ return remote;
332
+ }
333
+
334
+ // Batch witness signature + summed supply-tier price for several vaults.
335
+ async function requestRemoteBatchClaimSignature(
336
+ ctx: SdkContext,
337
+ tokenIds: string[],
338
+ signature: string,
339
+ chainId: number
340
+ ): Promise<any> {
341
+ const response = await fetch(`${ctx.baseUrl}/claim-curated-bulk`, {
342
+ method: 'POST',
343
+ headers: { 'Content-Type': 'application/json' },
344
+ body: JSON.stringify({ tokenIds, signature, chainId: chainId.toString() }),
345
+ });
346
+ const remote = await response.json();
347
+ if (!remote?.success) {
348
+ throw new Error(remote?.msg || remote?.error || 'Failed to get batch claim signature');
349
+ }
350
+ return remote;
351
+ }
352
+
353
+ // Client path: burn several vaults in one tx via batchClaimWithSignedPrice. Only
354
+ // performs the on-chain claim (Step 1) for the batch; reveal each vault's keys
355
+ // with performClaimChainWithClient afterward (which will skip Step 1 as claimed).
356
+ export async function performBatchClaimEvm(
357
+ ctx: SdkContext,
358
+ client: EmblemVaultClient,
359
+ tokenIds: string[],
360
+ chainId: number,
361
+ callback?: ProgressCallback
362
+ ): Promise<{ txHash: string }> {
363
+ callback?.('Initializing EVM signer...');
364
+ const { ethers } = await import('ethers');
365
+ const provider = new ethers.providers.JsonRpcProvider(getEvmRpcUrl(chainId));
366
+ const wallet = await client.toEthersWallet(provider);
367
+ wallet.setChainId?.(chainId);
368
+
369
+ // One wallet signature over the sorted, comma-joined ids (order-independent).
370
+ const sorted = [...tokenIds.map(String)].sort().join(',');
371
+ callback?.('Signing batch claim authorization...');
372
+ const signature = await wallet.signMessage(`Claim: ${sorted}`);
373
+
374
+ callback?.('Requesting batch claim price...');
375
+ const remote = await requestRemoteBatchClaimSignature(ctx, tokenIds, signature, chainId);
376
+
377
+ const HANDLER_BATCH_ABI = [
378
+ 'function batchClaimWithSignedPrice(address[] nftAddresses, uint256[] tokenIds, address _payment, uint256 _price, uint256 _nonce, bytes _signature) external payable'
379
+ ];
380
+ const handlerAddress = getHandlerContractAddress(chainId);
381
+ const iface = new ethers.utils.Interface(HANDLER_BATCH_ABI);
382
+
383
+ const price = ethers.BigNumber.from(remote._price);
384
+ const isEth = remote._payment === ZERO_ADDRESS;
385
+
386
+ const data = iface.encodeFunctionData('batchClaimWithSignedPrice', [
387
+ remote._nftAddresses,
388
+ remote._tokenIds.map((t: string) => ethers.BigNumber.from(t)),
389
+ remote._payment,
390
+ price,
391
+ ethers.BigNumber.from(remote._nonce),
392
+ remote._signature,
393
+ ]);
394
+
395
+ callback?.('Submitting batch claim transaction...');
396
+ const tx = await wallet.sendTransaction({
397
+ to: handlerAddress,
398
+ data,
399
+ value: isEth ? price : ethers.constants.Zero,
400
+ });
401
+
402
+ callback?.('Waiting for batch claim confirmation...', { txHash: tx.hash });
403
+ await tx.wait();
404
+ callback?.('On-chain batch claim complete!');
405
+ return { txHash: tx.hash };
406
+ }
407
+
298
408
  async function requestRemoteUnvaultSignature(
299
409
  ctx: SdkContext,
300
410
  tokenId: string,
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ import type {
14
14
  BulkMintResponse,
15
15
  } from './types';
16
16
  import { NFT_DATA, checkContentType, decryptKeys, fetchData, generateTemplate, genericGuard, getHandlerContract, getLegacyContract, getQuoteContractObject, getSatsConnectAddress, getTorusKeys, metadataAllProjects, metadataObj2Arr, signPSBT, templateGuard } from './utils';
17
- import { generateTaprootAddressFromMnemonic, getPsbtTxnSize } from './derive';
17
+ import { generateTaprootAddressFromMnemonic, getPsbtTxnSize, deriveSolanaFromMnemonic } from './derive';
18
18
  import {
19
19
  ETHEREUM_MAINNET_CHAIN_ID,
20
20
  } from './constants';
@@ -24,7 +24,7 @@ import {
24
24
  requiresOnChainUnvault,
25
25
  getClaimIdentifier,
26
26
  } from './vault-utils';
27
- import { performMintEvm, performClaimEvm, deleteVaultEvm } from './evm-operations';
27
+ import { performMintEvm, performClaimEvm, performBatchClaimEvm, deleteVaultEvm } from './evm-operations';
28
28
 
29
29
  const SDK_VERSION = '__SDK_VERSION__';
30
30
 
@@ -401,6 +401,20 @@ class EmblemVaultSDK {
401
401
  return remoteMintResponse
402
402
  }
403
403
 
404
+ // Witness signature + supply-tier price for a claim (the on-chain burn before
405
+ // key reveal). `signature` is the user's "Claim: <tokenId>" wallet signature.
406
+ async requestRemoteClaimSignature(web3: any, tokenId: string, signature: string, callback: any = null) {
407
+ if (callback) { callback('requesting Remote Claim signature')}
408
+ const chainId = await web3.eth.getChainId();
409
+ let url = `${this.baseUrl}/claim-curated`;
410
+ let remoteClaimResponse = await fetchData(url, this.apiKey, 'POST', {tokenId: tokenId, signature: signature, chainId: chainId.toString()});
411
+ if (remoteClaimResponse.error || remoteClaimResponse.err) {
412
+ throw new Error(remoteClaimResponse.error || remoteClaimResponse.msg || 'Failed to get claim signature')
413
+ }
414
+ if (callback) { callback(`remote Claim signature`, remoteClaimResponse)}
415
+ return remoteClaimResponse
416
+ }
417
+
404
418
  // ** Bulk Mint **
405
419
  //
406
420
  // Builds the deterministic message a user signs to authorize a bulk curated
@@ -512,6 +526,15 @@ class EmblemVaultSDK {
512
526
  return ukeys
513
527
  }
514
528
 
529
+ // Re-derive the Solana key from a claimed vault's decrypted mnemonic. Solana is
530
+ // ed25519 (not secp256k1), so it can't go through the bitcoinjs derivation the other
531
+ // coins use — this uses the SLIP-0010 ed25519 derivation in ./derive (matching the
532
+ // serverless creation-side byte-for-byte). Returns { address, secretKey (base58,
533
+ // Phantom-importable), path, coin:'SOL' }.
534
+ deriveSolanaKeys(phrase: string) {
535
+ return deriveSolanaFromMnemonic(phrase);
536
+ }
537
+
515
538
  async getQuote(web3: any, amount: number, callback: any = null) {
516
539
  if (callback) { callback('requesting Quote')}
517
540
  let quoteContract = await getQuoteContractObject(web3);
@@ -567,25 +590,34 @@ class EmblemVaultSDK {
567
590
 
568
591
 
569
592
  async performBurn(web3: any, tokenId: any, callback: any = null) {
570
- let metadata: any = await this.fetchMetadata(tokenId);
571
- let targetContract: any = await this.fetchCuratedContractByName(metadata.targetContract.name);
572
593
  if (callback) { callback('performing Burn')}
573
594
  const accounts = await web3.eth.getAccounts();
574
- const chainId = await web3.eth.getChainId();
575
595
  let handlerContract = await getHandlerContract(web3);
576
-
577
- // Dynamically fetch the current gas price
596
+
597
+ // The handler no longer has a free claim(address,uint256); claiming requires
598
+ // a witness-signed price via claimWithSignedPrice. Get the user's "Claim:"
599
+ // signature, then the server-signed price for this asset (supply tier), then
600
+ // call claimWithSignedPrice with the server-derived on-chain tokenId + payment.
601
+ const claimSig = await this.requestLocalClaimSignature(web3, tokenId, null, callback);
602
+ const remote = await this.requestRemoteClaimSignature(web3, tokenId, claimSig, callback);
603
+
604
+ const price = remote._price;
605
+ const isEth = remote._payment === '0x0000000000000000000000000000000000000000';
606
+
578
607
  const gasPrice = await web3.eth.getGasPrice();
579
-
580
- let createdTxObject = handlerContract.methods.claim(targetContract[chainId], targetContract.collectionType == 'ERC721a' ? tokenId : targetContract.tokenId)
581
- // Estimate gas limit for the transaction
582
- const estimatedGas = await createdTxObject.estimateGas({from: accounts[0]});
583
-
584
- let burnResponse = await createdTxObject.send({
585
- from: accounts[0],
586
- gasPrice: gasPrice,
587
- gas: estimatedGas
588
- }).on('transactionHash', (hash: any) => {
608
+ let createdTxObject = handlerContract.methods.claimWithSignedPrice(
609
+ remote._nftAddress,
610
+ remote._tokenId,
611
+ remote._payment,
612
+ price,
613
+ remote._nonce,
614
+ remote._signature
615
+ );
616
+ const sendOpts: any = { from: accounts[0], gasPrice, value: isEth ? price : '0' };
617
+ sendOpts.gas = await createdTxObject.estimateGas(sendOpts);
618
+
619
+ let burnResponse = await createdTxObject.send(sendOpts)
620
+ .on('transactionHash', (hash: any) => {
589
621
  if (callback) callback(`Transaction submitted. Hash`, hash);
590
622
  })
591
623
  .on('confirmation', (confirmationNumber: any, receipt: any) => {
@@ -594,11 +626,74 @@ class EmblemVaultSDK {
594
626
  .on('error', (error: { message: any; }) => {
595
627
  if (callback) callback(`Transaction Error`, error.message );
596
628
  });
597
-
629
+
598
630
  if (callback) { callback('Burn Complete')}
599
631
  return burnResponse;
600
632
  }
601
-
633
+
634
+ // Wallet signature authorizing a batch claim. Must match the server's expected
635
+ // message: "Claim: <sorted tokenIds joined by ','>" (order-independent).
636
+ async requestLocalBatchClaimSignature(web3: any, tokenIds: string[], callback: any = null) {
637
+ if (callback) { callback('requesting User Batch Claim Signature') }
638
+ const sorted = [...tokenIds.map(String)].sort().join(',');
639
+ const message = `Claim: ${sorted}`;
640
+ const accounts = await web3.eth.getAccounts();
641
+ const signature = await web3.eth.personal.sign(message, accounts[0], '');
642
+ return signature;
643
+ }
644
+
645
+ // Fetch the batch witness signature + summed supply-tier price for several vaults.
646
+ async requestRemoteBatchClaimSignature(web3: any, tokenIds: string[], signature: string, callback: any = null) {
647
+ if (callback) { callback('requesting Remote Batch Claim signature') }
648
+ const chainId = await web3.eth.getChainId();
649
+ let url = `${this.baseUrl}/claim-curated-bulk`;
650
+ let remote = await fetchData(url, this.apiKey, 'POST', { tokenIds, signature, chainId: chainId.toString() });
651
+ if (remote.error || remote.err || remote.success === false) {
652
+ throw new Error(remote.error || remote.msg || 'Failed to get batch claim signature');
653
+ }
654
+ return remote;
655
+ }
656
+
657
+ // Burn several vaults in one tx via batchClaimWithSignedPrice. One wallet
658
+ // signature + one witness signature + summed supply-tier price for the batch.
659
+ async performBatchBurn(web3: any, tokenIds: string[], callback: any = null) {
660
+ if (callback) { callback('performing Batch Burn') }
661
+ const accounts = await web3.eth.getAccounts();
662
+ let handlerContract = await getHandlerContract(web3);
663
+
664
+ const claimSig = await this.requestLocalBatchClaimSignature(web3, tokenIds, callback);
665
+ const remote = await this.requestRemoteBatchClaimSignature(web3, tokenIds, claimSig, callback);
666
+
667
+ const price = remote._price;
668
+ const isEth = remote._payment === '0x0000000000000000000000000000000000000000';
669
+ const value = isEth ? price : '0';
670
+
671
+ const gasPrice = await web3.eth.getGasPrice();
672
+ let createdTxObject = handlerContract.methods.batchClaimWithSignedPrice(
673
+ remote._nftAddresses,
674
+ remote._tokenIds,
675
+ remote._payment,
676
+ price,
677
+ remote._nonce,
678
+ remote._signature
679
+ );
680
+ const gas = await createdTxObject.estimateGas({ from: accounts[0], value });
681
+
682
+ let burnResponse = await createdTxObject.send({ from: accounts[0], value, gasPrice, gas })
683
+ .on('transactionHash', (hash: any) => {
684
+ if (callback) callback(`Transaction submitted. Hash`, hash);
685
+ })
686
+ .on('confirmation', (confirmationNumber: any, receipt: any) => {
687
+ if (callback) callback(`Batch Burn Complete. Confirmation Number`, confirmationNumber);
688
+ })
689
+ .on('error', (error: { message: any; }) => {
690
+ if (callback) callback(`Transaction Error`, error.message);
691
+ });
692
+
693
+ if (callback) { callback('Batch Burn Complete') }
694
+ return burnResponse;
695
+ }
696
+
602
697
 
603
698
  async contentTypeReport(url: string) {
604
699
  return await checkContentType(url)
@@ -699,6 +794,18 @@ class EmblemVaultSDK {
699
794
  return performClaimEvm(this.getSdkContext(), client, tokenId, chainId as number, metadata, claimIdentifier, vaultIsV2, needsOnChainUnvault, callback);
700
795
  }
701
796
 
797
+ // Client-path batch claim: burn several vaults in one tx (Step 1 only). Reveal
798
+ // each vault's keys afterward with performClaimChainWithClient (skips Step 1).
799
+ async performBatchBurnWithClient(
800
+ client: EmblemVaultClient,
801
+ tokenIds: string[],
802
+ chainId: number | 'solana' = ETHEREUM_MAINNET_CHAIN_ID,
803
+ callback?: ProgressCallback
804
+ ): Promise<{ txHash: string }> {
805
+ getChainConfig(chainId);
806
+ return performBatchClaimEvm(this.getSdkContext(), client, tokenIds, chainId as number, callback);
807
+ }
808
+
702
809
  async deleteVaultWithClient(
703
810
  client: EmblemVaultClient,
704
811
  tokenId: string,
package/types/derive.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ export declare const deriveSolanaFromMnemonic: (phrase: string) => {
2
+ address: string;
3
+ secretKey: string;
4
+ path: string;
5
+ coin: string;
6
+ };
1
7
  declare global {
2
8
  interface Window {
3
9
  bitcoin: any;
@@ -2,3 +2,6 @@ import type { EmblemVaultClient, ProgressCallback, MintResult, ClaimResult, Meta
2
2
  export declare function performMintEvm(ctx: SdkContext, client: EmblemVaultClient, tokenId: string, chainId: number, callback?: ProgressCallback): Promise<MintResult>;
3
3
  export declare function performClaimEvm(ctx: SdkContext, client: EmblemVaultClient, tokenId: string, chainId: number, metadata: MetaData, claimIdentifier: string, vaultIsV2: boolean, needsOnChainUnvault: boolean, callback?: ProgressCallback): Promise<ClaimResult>;
4
4
  export declare function deleteVaultEvm(client: EmblemVaultClient, tokenId: string, chainId: number, callback?: ProgressCallback): Promise<boolean>;
5
+ export declare function performBatchClaimEvm(ctx: SdkContext, client: EmblemVaultClient, tokenIds: string[], chainId: number, callback?: ProgressCallback): Promise<{
6
+ txHash: string;
7
+ }>;
package/types/index.d.ts CHANGED
@@ -52,6 +52,7 @@ declare class EmblemVaultSDK {
52
52
  requestLocalMintSignature(web3: any, tokenId: string, callback?: any): Promise<any>;
53
53
  requestLocalClaimSignature(web3: any, tokenId: string, serialNumber: any, callback?: any): Promise<any>;
54
54
  requestRemoteMintSignature(web3: any, tokenId: string, signature: string, callback?: any): Promise<any>;
55
+ requestRemoteClaimSignature(web3: any, tokenId: string, signature: string, callback?: any): Promise<any>;
55
56
  generateBulkMintMessage(tokenIds: string[]): string;
56
57
  isV2Contract(metadata: any, chainId: number): boolean;
57
58
  requestBulkMintSignature(request: BulkMintRequest, callback?: any): Promise<BulkMintResponse>;
@@ -61,9 +62,18 @@ declare class EmblemVaultSDK {
61
62
  privateKey: any;
62
63
  }>;
63
64
  decryptVaultKeys(tokenId: string, dkeys: any, callback?: any): Promise<any>;
65
+ deriveSolanaKeys(phrase: string): {
66
+ address: string;
67
+ secretKey: string;
68
+ path: string;
69
+ coin: string;
70
+ };
64
71
  getQuote(web3: any, amount: number, callback?: any): Promise<BigNumber>;
65
72
  performMint(web3: any, quote: any, remoteMintSig: any, callback?: any): Promise<any>;
66
73
  performBurn(web3: any, tokenId: any, callback?: any): Promise<any>;
74
+ requestLocalBatchClaimSignature(web3: any, tokenIds: string[], callback?: any): Promise<any>;
75
+ requestRemoteBatchClaimSignature(web3: any, tokenIds: string[], signature: string, callback?: any): Promise<any>;
76
+ performBatchBurn(web3: any, tokenIds: string[], callback?: any): Promise<any>;
67
77
  contentTypeReport(url: string): Promise<unknown>;
68
78
  legacyBalanceFromContractByAddress(web3: any, address: string): Promise<number[]>;
69
79
  refreshLegacyOwnership(web3: any, address: string): Promise<void>;
@@ -72,6 +82,9 @@ declare class EmblemVaultSDK {
72
82
  private getSdkContext;
73
83
  performMintChainWithClient(client: EmblemVaultClient, tokenId: string, chainId?: number | 'solana', callback?: ProgressCallback): Promise<MintResult>;
74
84
  performClaimChainWithClient(client: EmblemVaultClient, tokenId: string, chainId?: number | 'solana', callback?: ProgressCallback): Promise<ClaimResult>;
85
+ performBatchBurnWithClient(client: EmblemVaultClient, tokenIds: string[], chainId?: number | 'solana', callback?: ProgressCallback): Promise<{
86
+ txHash: string;
87
+ }>;
75
88
  deleteVaultWithClient(client: EmblemVaultClient, tokenId: string, chainId?: number | 'solana', callback?: ProgressCallback): Promise<boolean>;
76
89
  sweepVaultUsingPhrase(phrase: string, satsPerByte?: number, broadcast?: boolean): Promise<unknown>;
77
90
  }