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.
package/dist/derive.js CHANGED
@@ -42,12 +42,62 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
42
42
  });
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.getPsbtTxnSize = exports.generateTaprootAddressFromMnemonic = void 0;
45
+ exports.getPsbtTxnSize = exports.generateTaprootAddressFromMnemonic = exports.deriveSolanaFromMnemonic = void 0;
46
46
  const bip32_1 = require("bip32");
47
47
  const bip39 = __importStar(require("bip39"));
48
48
  // import bitcoin from "bitcoinjs-lib";
49
49
  const ecc = __importStar(require("@bitcoin-js/tiny-secp256k1-asmjs"));
50
+ const hmac_1 = require("@noble/hashes/hmac");
51
+ const sha512_1 = require("@noble/hashes/sha512");
52
+ const ed25519_1 = require("@noble/curves/ed25519");
53
+ // bs58@4 ships no type declarations; require it (CJS) to avoid TS7016.
54
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
55
+ const bs58 = require("bs58");
50
56
  const bip32 = (0, bip32_1.BIP32Factory)(ecc);
57
+ // --- Solana (ed25519 SLIP-0010) HD derivation ---
58
+ // Solana uses ed25519, NOT secp256k1, so bip32/bitcoinjs can't derive it. We do the
59
+ // SLIP-0010 ed25519 derivation via @noble (browser-safe, already a dependency; NOT
60
+ // @solana/web3.js — that heavy dep is the conflict that got SOL derivation disabled).
61
+ // Path m/44'/501'/0'/0' (all hardened) is the Phantom/Solflare standard. This matches
62
+ // the serverless vault-creation derivation byte-for-byte, so a claimed vault's SOL key
63
+ // re-derives to the stored address.
64
+ const SOLANA_PATH = "m/44'/501'/0'/0'";
65
+ function slip10DeriveEd25519Seed(pathStr, seed) {
66
+ let I = (0, hmac_1.hmac)(sha512_1.sha512, new TextEncoder().encode("ed25519 seed"), seed);
67
+ let key = I.slice(0, 32);
68
+ let chain = I.slice(32);
69
+ for (const seg of pathStr.split("/").slice(1)) {
70
+ const idx = ((parseInt(seg, 10) | 0x80000000) >>> 0); // all segments hardened
71
+ const data = new Uint8Array(1 + 32 + 4);
72
+ data[0] = 0;
73
+ data.set(key, 1);
74
+ data[33] = (idx >>> 24) & 255;
75
+ data[34] = (idx >>> 16) & 255;
76
+ data[35] = (idx >>> 8) & 255;
77
+ data[36] = idx & 255;
78
+ I = (0, hmac_1.hmac)(sha512_1.sha512, chain, data);
79
+ key = I.slice(0, 32);
80
+ chain = I.slice(32);
81
+ }
82
+ return key; // 32-byte ed25519 seed
83
+ }
84
+ // Derive the Solana address + Phantom-importable secret key from a vault mnemonic.
85
+ // Returns { address (base58 pubkey), secretKey (base58 of the 64-byte secret), path, coin }.
86
+ const deriveSolanaFromMnemonic = (phrase) => {
87
+ const seed = bip39.mnemonicToSeedSync(phrase);
88
+ const priv = slip10DeriveEd25519Seed(SOLANA_PATH, new Uint8Array(seed));
89
+ const pub = ed25519_1.ed25519.getPublicKey(priv);
90
+ const secret = new Uint8Array(64);
91
+ secret.set(priv, 0);
92
+ secret.set(pub, 32);
93
+ return {
94
+ address: bs58.encode(pub),
95
+ secretKey: bs58.encode(secret), // 64-byte secret, base58 — import into Phantom/Solflare
96
+ path: SOLANA_PATH,
97
+ coin: "SOL",
98
+ };
99
+ };
100
+ exports.deriveSolanaFromMnemonic = deriveSolanaFromMnemonic;
51
101
  const generateTaprootAddressFromMnemonic = (phrase) => __awaiter(void 0, void 0, void 0, function* () {
52
102
  let bitcoin = window.bitcoin;
53
103
  let mainnet = bitcoin.networks.mainnet;
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.performMintEvm = performMintEvm;
46
46
  exports.performClaimEvm = performClaimEvm;
47
47
  exports.deleteVaultEvm = deleteVaultEvm;
48
+ exports.performBatchClaimEvm = performBatchClaimEvm;
48
49
  const constants_1 = require("./constants");
49
50
  const signing_messages_1 = require("./signing-messages");
50
51
  const vault_utils_1 = require("./vault-utils");
@@ -97,7 +98,7 @@ function performClaimEvm(ctx, client, tokenId, chainId, metadata, claimIdentifie
97
98
  const targetContractAddress = ((_b = metadata.targetContract) === null || _b === void 0 ? void 0 : _b[chainId]) || metadata.collectionAddress;
98
99
  if (targetContractAddress) {
99
100
  callback === null || callback === void 0 ? void 0 : callback('Performing on-chain claim...');
100
- yield performLegacyClaim(wallet, targetContractAddress, claimIdentifier, chainId, callback);
101
+ yield performLegacyClaim(ctx, wallet, targetContractAddress, claimIdentifier, chainId, callback);
101
102
  }
102
103
  }
103
104
  }
@@ -211,29 +212,117 @@ function performOnChainUnvault(ctx, wallet, tokenId, claimIdentifier, nftAddress
211
212
  * Perform legacy claim by calling the handler contract's claim function
212
213
  * This is for non-V2 vaults that don't use signed price unvaulting
213
214
  */
214
- function performLegacyClaim(wallet, targetContractAddress, tokenId, chainId, callback) {
215
+ function performLegacyClaim(ctx, wallet, targetContractAddress, tokenId, chainId, callback) {
215
216
  return __awaiter(this, void 0, void 0, function* () {
216
217
  const { ethers } = yield Promise.resolve().then(() => __importStar(require('ethers')));
217
- // Handler contract ABI for claim function
218
+ // The handler's free claim(address,uint256) was retired; claiming requires a
219
+ // witness-signed price via claimWithSignedPrice. Sign "Claim: <tokenId>", get
220
+ // the server-signed price for this asset (supply tier), then call the contract
221
+ // with the server-derived on-chain tokenId + payment.
222
+ callback === null || callback === void 0 ? void 0 : callback('Signing claim authorization...');
223
+ const claimSig = yield wallet.signMessage(`Claim: ${tokenId}`);
224
+ callback === null || callback === void 0 ? void 0 : callback('Requesting claim price...');
225
+ const remote = yield requestRemoteClaimSignature(ctx, tokenId, claimSig, chainId);
218
226
  const HANDLER_CLAIM_ABI = [
219
- 'function claim(address _nftAddress, uint256 _tokenId) external'
227
+ 'function claimWithSignedPrice(address _nftAddress, uint256 tokenId, address _payment, uint256 _price, uint256 _nonce, bytes _signature) external payable'
220
228
  ];
221
229
  const handlerAddress = (0, vault_utils_1.getHandlerContractAddress)(chainId);
222
230
  const iface = new ethers.utils.Interface(HANDLER_CLAIM_ABI);
223
- const data = iface.encodeFunctionData('claim', [
224
- targetContractAddress,
225
- BigInt(tokenId),
231
+ const price = ethers.BigNumber.from(remote._price);
232
+ const isEth = remote._payment === '0x0000000000000000000000000000000000000000';
233
+ const data = iface.encodeFunctionData('claimWithSignedPrice', [
234
+ remote._nftAddress,
235
+ ethers.BigNumber.from(remote._tokenId),
236
+ remote._payment,
237
+ price,
238
+ ethers.BigNumber.from(remote._nonce),
239
+ remote._signature,
226
240
  ]);
227
241
  callback === null || callback === void 0 ? void 0 : callback('Submitting claim transaction...');
228
242
  const tx = yield wallet.sendTransaction({
229
243
  to: handlerAddress,
230
244
  data,
245
+ value: isEth ? price : ethers.constants.Zero,
231
246
  });
232
247
  callback === null || callback === void 0 ? void 0 : callback('Waiting for claim confirmation...', { txHash: tx.hash });
233
248
  yield tx.wait();
234
249
  callback === null || callback === void 0 ? void 0 : callback('On-chain claim complete!');
235
250
  });
236
251
  }
252
+ // Witness signature + supply-tier price for a legacy (non-diamond) claim.
253
+ function requestRemoteClaimSignature(ctx, tokenId, signature, chainId) {
254
+ return __awaiter(this, void 0, void 0, function* () {
255
+ const response = yield fetch(`${ctx.baseUrl}/claim-curated`, {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({ tokenId, signature, chainId: chainId.toString() }),
259
+ });
260
+ const remote = yield response.json();
261
+ if (!(remote === null || remote === void 0 ? void 0 : remote.success)) {
262
+ throw new Error((remote === null || remote === void 0 ? void 0 : remote.msg) || (remote === null || remote === void 0 ? void 0 : remote.error) || 'Failed to get claim signature');
263
+ }
264
+ return remote;
265
+ });
266
+ }
267
+ // Batch witness signature + summed supply-tier price for several vaults.
268
+ function requestRemoteBatchClaimSignature(ctx, tokenIds, signature, chainId) {
269
+ return __awaiter(this, void 0, void 0, function* () {
270
+ const response = yield fetch(`${ctx.baseUrl}/claim-curated-bulk`, {
271
+ method: 'POST',
272
+ headers: { 'Content-Type': 'application/json' },
273
+ body: JSON.stringify({ tokenIds, signature, chainId: chainId.toString() }),
274
+ });
275
+ const remote = yield response.json();
276
+ if (!(remote === null || remote === void 0 ? void 0 : remote.success)) {
277
+ throw new Error((remote === null || remote === void 0 ? void 0 : remote.msg) || (remote === null || remote === void 0 ? void 0 : remote.error) || 'Failed to get batch claim signature');
278
+ }
279
+ return remote;
280
+ });
281
+ }
282
+ // Client path: burn several vaults in one tx via batchClaimWithSignedPrice. Only
283
+ // performs the on-chain claim (Step 1) for the batch; reveal each vault's keys
284
+ // with performClaimChainWithClient afterward (which will skip Step 1 as claimed).
285
+ function performBatchClaimEvm(ctx, client, tokenIds, chainId, callback) {
286
+ return __awaiter(this, void 0, void 0, function* () {
287
+ var _a;
288
+ callback === null || callback === void 0 ? void 0 : callback('Initializing EVM signer...');
289
+ const { ethers } = yield Promise.resolve().then(() => __importStar(require('ethers')));
290
+ const provider = new ethers.providers.JsonRpcProvider(getEvmRpcUrl(chainId));
291
+ const wallet = yield client.toEthersWallet(provider);
292
+ (_a = wallet.setChainId) === null || _a === void 0 ? void 0 : _a.call(wallet, chainId);
293
+ // One wallet signature over the sorted, comma-joined ids (order-independent).
294
+ const sorted = [...tokenIds.map(String)].sort().join(',');
295
+ callback === null || callback === void 0 ? void 0 : callback('Signing batch claim authorization...');
296
+ const signature = yield wallet.signMessage(`Claim: ${sorted}`);
297
+ callback === null || callback === void 0 ? void 0 : callback('Requesting batch claim price...');
298
+ const remote = yield requestRemoteBatchClaimSignature(ctx, tokenIds, signature, chainId);
299
+ const HANDLER_BATCH_ABI = [
300
+ 'function batchClaimWithSignedPrice(address[] nftAddresses, uint256[] tokenIds, address _payment, uint256 _price, uint256 _nonce, bytes _signature) external payable'
301
+ ];
302
+ const handlerAddress = (0, vault_utils_1.getHandlerContractAddress)(chainId);
303
+ const iface = new ethers.utils.Interface(HANDLER_BATCH_ABI);
304
+ const price = ethers.BigNumber.from(remote._price);
305
+ const isEth = remote._payment === constants_1.ZERO_ADDRESS;
306
+ const data = iface.encodeFunctionData('batchClaimWithSignedPrice', [
307
+ remote._nftAddresses,
308
+ remote._tokenIds.map((t) => ethers.BigNumber.from(t)),
309
+ remote._payment,
310
+ price,
311
+ ethers.BigNumber.from(remote._nonce),
312
+ remote._signature,
313
+ ]);
314
+ callback === null || callback === void 0 ? void 0 : callback('Submitting batch claim transaction...');
315
+ const tx = yield wallet.sendTransaction({
316
+ to: handlerAddress,
317
+ data,
318
+ value: isEth ? price : ethers.constants.Zero,
319
+ });
320
+ callback === null || callback === void 0 ? void 0 : callback('Waiting for batch claim confirmation...', { txHash: tx.hash });
321
+ yield tx.wait();
322
+ callback === null || callback === void 0 ? void 0 : callback('On-chain batch claim complete!');
323
+ return { txHash: tx.hash };
324
+ });
325
+ }
237
326
  function requestRemoteUnvaultSignature(ctx, tokenId, signature, chainId) {
238
327
  return __awaiter(this, void 0, void 0, function* () {
239
328
  const response = yield fetch(`${ctx.baseUrl}/unvault-curated`, {
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ const derive_1 = require("./derive");
48
48
  const constants_1 = require("./constants");
49
49
  const vault_utils_1 = require("./vault-utils");
50
50
  const evm_operations_1 = require("./evm-operations");
51
- const SDK_VERSION = '2.11.0';
51
+ const SDK_VERSION = '2.13.0';
52
52
  class EmblemVaultSDK {
53
53
  constructor(apiKey, baseUrl, v3Url, sigUrl) {
54
54
  this.apiKey = apiKey;
@@ -455,6 +455,25 @@ class EmblemVaultSDK {
455
455
  return remoteMintResponse;
456
456
  });
457
457
  }
458
+ // Witness signature + supply-tier price for a claim (the on-chain burn before
459
+ // key reveal). `signature` is the user's "Claim: <tokenId>" wallet signature.
460
+ requestRemoteClaimSignature(web3_1, tokenId_1, signature_1) {
461
+ return __awaiter(this, arguments, void 0, function* (web3, tokenId, signature, callback = null) {
462
+ if (callback) {
463
+ callback('requesting Remote Claim signature');
464
+ }
465
+ const chainId = yield web3.eth.getChainId();
466
+ let url = `${this.baseUrl}/claim-curated`;
467
+ let remoteClaimResponse = yield (0, utils_1.fetchData)(url, this.apiKey, 'POST', { tokenId: tokenId, signature: signature, chainId: chainId.toString() });
468
+ if (remoteClaimResponse.error || remoteClaimResponse.err) {
469
+ throw new Error(remoteClaimResponse.error || remoteClaimResponse.msg || 'Failed to get claim signature');
470
+ }
471
+ if (callback) {
472
+ callback(`remote Claim signature`, remoteClaimResponse);
473
+ }
474
+ return remoteClaimResponse;
475
+ });
476
+ }
458
477
  // ** Bulk Mint **
459
478
  //
460
479
  // Builds the deterministic message a user signs to authorize a bulk curated
@@ -585,6 +604,14 @@ class EmblemVaultSDK {
585
604
  return ukeys;
586
605
  });
587
606
  }
607
+ // Re-derive the Solana key from a claimed vault's decrypted mnemonic. Solana is
608
+ // ed25519 (not secp256k1), so it can't go through the bitcoinjs derivation the other
609
+ // coins use — this uses the SLIP-0010 ed25519 derivation in ./derive (matching the
610
+ // serverless creation-side byte-for-byte). Returns { address, secretKey (base58,
611
+ // Phantom-importable), path, coin:'SOL' }.
612
+ deriveSolanaKeys(phrase) {
613
+ return (0, derive_1.deriveSolanaFromMnemonic)(phrase);
614
+ }
588
615
  getQuote(web3_1, amount_1) {
589
616
  return __awaiter(this, arguments, void 0, function* (web3, amount, callback = null) {
590
617
  if (callback) {
@@ -639,24 +666,25 @@ class EmblemVaultSDK {
639
666
  }
640
667
  performBurn(web3_1, tokenId_1) {
641
668
  return __awaiter(this, arguments, void 0, function* (web3, tokenId, callback = null) {
642
- let metadata = yield this.fetchMetadata(tokenId);
643
- let targetContract = yield this.fetchCuratedContractByName(metadata.targetContract.name);
644
669
  if (callback) {
645
670
  callback('performing Burn');
646
671
  }
647
672
  const accounts = yield web3.eth.getAccounts();
648
- const chainId = yield web3.eth.getChainId();
649
673
  let handlerContract = yield (0, utils_1.getHandlerContract)(web3);
650
- // Dynamically fetch the current gas price
674
+ // The handler no longer has a free claim(address,uint256); claiming requires
675
+ // a witness-signed price via claimWithSignedPrice. Get the user's "Claim:"
676
+ // signature, then the server-signed price for this asset (supply tier), then
677
+ // call claimWithSignedPrice with the server-derived on-chain tokenId + payment.
678
+ const claimSig = yield this.requestLocalClaimSignature(web3, tokenId, null, callback);
679
+ const remote = yield this.requestRemoteClaimSignature(web3, tokenId, claimSig, callback);
680
+ const price = remote._price;
681
+ const isEth = remote._payment === '0x0000000000000000000000000000000000000000';
651
682
  const gasPrice = yield web3.eth.getGasPrice();
652
- let createdTxObject = handlerContract.methods.claim(targetContract[chainId], targetContract.collectionType == 'ERC721a' ? tokenId : targetContract.tokenId);
653
- // Estimate gas limit for the transaction
654
- const estimatedGas = yield createdTxObject.estimateGas({ from: accounts[0] });
655
- let burnResponse = yield createdTxObject.send({
656
- from: accounts[0],
657
- gasPrice: gasPrice,
658
- gas: estimatedGas
659
- }).on('transactionHash', (hash) => {
683
+ let createdTxObject = handlerContract.methods.claimWithSignedPrice(remote._nftAddress, remote._tokenId, remote._payment, price, remote._nonce, remote._signature);
684
+ const sendOpts = { from: accounts[0], gasPrice, value: isEth ? price : '0' };
685
+ sendOpts.gas = yield createdTxObject.estimateGas(sendOpts);
686
+ let burnResponse = yield createdTxObject.send(sendOpts)
687
+ .on('transactionHash', (hash) => {
660
688
  if (callback)
661
689
  callback(`Transaction submitted. Hash`, hash);
662
690
  })
@@ -674,6 +702,71 @@ class EmblemVaultSDK {
674
702
  return burnResponse;
675
703
  });
676
704
  }
705
+ // Wallet signature authorizing a batch claim. Must match the server's expected
706
+ // message: "Claim: <sorted tokenIds joined by ','>" (order-independent).
707
+ requestLocalBatchClaimSignature(web3_1, tokenIds_1) {
708
+ return __awaiter(this, arguments, void 0, function* (web3, tokenIds, callback = null) {
709
+ if (callback) {
710
+ callback('requesting User Batch Claim Signature');
711
+ }
712
+ const sorted = [...tokenIds.map(String)].sort().join(',');
713
+ const message = `Claim: ${sorted}`;
714
+ const accounts = yield web3.eth.getAccounts();
715
+ const signature = yield web3.eth.personal.sign(message, accounts[0], '');
716
+ return signature;
717
+ });
718
+ }
719
+ // Fetch the batch witness signature + summed supply-tier price for several vaults.
720
+ requestRemoteBatchClaimSignature(web3_1, tokenIds_1, signature_1) {
721
+ return __awaiter(this, arguments, void 0, function* (web3, tokenIds, signature, callback = null) {
722
+ if (callback) {
723
+ callback('requesting Remote Batch Claim signature');
724
+ }
725
+ const chainId = yield web3.eth.getChainId();
726
+ let url = `${this.baseUrl}/claim-curated-bulk`;
727
+ let remote = yield (0, utils_1.fetchData)(url, this.apiKey, 'POST', { tokenIds, signature, chainId: chainId.toString() });
728
+ if (remote.error || remote.err || remote.success === false) {
729
+ throw new Error(remote.error || remote.msg || 'Failed to get batch claim signature');
730
+ }
731
+ return remote;
732
+ });
733
+ }
734
+ // Burn several vaults in one tx via batchClaimWithSignedPrice. One wallet
735
+ // signature + one witness signature + summed supply-tier price for the batch.
736
+ performBatchBurn(web3_1, tokenIds_1) {
737
+ return __awaiter(this, arguments, void 0, function* (web3, tokenIds, callback = null) {
738
+ if (callback) {
739
+ callback('performing Batch Burn');
740
+ }
741
+ const accounts = yield web3.eth.getAccounts();
742
+ let handlerContract = yield (0, utils_1.getHandlerContract)(web3);
743
+ const claimSig = yield this.requestLocalBatchClaimSignature(web3, tokenIds, callback);
744
+ const remote = yield this.requestRemoteBatchClaimSignature(web3, tokenIds, claimSig, callback);
745
+ const price = remote._price;
746
+ const isEth = remote._payment === '0x0000000000000000000000000000000000000000';
747
+ const value = isEth ? price : '0';
748
+ const gasPrice = yield web3.eth.getGasPrice();
749
+ let createdTxObject = handlerContract.methods.batchClaimWithSignedPrice(remote._nftAddresses, remote._tokenIds, remote._payment, price, remote._nonce, remote._signature);
750
+ const gas = yield createdTxObject.estimateGas({ from: accounts[0], value });
751
+ let burnResponse = yield createdTxObject.send({ from: accounts[0], value, gasPrice, gas })
752
+ .on('transactionHash', (hash) => {
753
+ if (callback)
754
+ callback(`Transaction submitted. Hash`, hash);
755
+ })
756
+ .on('confirmation', (confirmationNumber, receipt) => {
757
+ if (callback)
758
+ callback(`Batch Burn Complete. Confirmation Number`, confirmationNumber);
759
+ })
760
+ .on('error', (error) => {
761
+ if (callback)
762
+ callback(`Transaction Error`, error.message);
763
+ });
764
+ if (callback) {
765
+ callback('Batch Burn Complete');
766
+ }
767
+ return burnResponse;
768
+ });
769
+ }
677
770
  contentTypeReport(url) {
678
771
  return __awaiter(this, void 0, void 0, function* () {
679
772
  return yield (0, utils_1.checkContentType)(url);
@@ -768,6 +861,14 @@ class EmblemVaultSDK {
768
861
  return (0, evm_operations_1.performClaimEvm)(this.getSdkContext(), client, tokenId, chainId, metadata, claimIdentifier, vaultIsV2, needsOnChainUnvault, callback);
769
862
  });
770
863
  }
864
+ // Client-path batch claim: burn several vaults in one tx (Step 1 only). Reveal
865
+ // each vault's keys afterward with performClaimChainWithClient (skips Step 1).
866
+ performBatchBurnWithClient(client_1, tokenIds_1) {
867
+ return __awaiter(this, arguments, void 0, function* (client, tokenIds, chainId = constants_1.ETHEREUM_MAINNET_CHAIN_ID, callback) {
868
+ (0, vault_utils_1.getChainConfig)(chainId);
869
+ return (0, evm_operations_1.performBatchClaimEvm)(this.getSdkContext(), client, tokenIds, chainId, callback);
870
+ });
871
+ }
771
872
  deleteVaultWithClient(client_1, tokenId_1) {
772
873
  return __awaiter(this, arguments, void 0, function* (client, tokenId, chainId = constants_1.ETHEREUM_MAINNET_CHAIN_ID, callback) {
773
874
  (0, utils_1.genericGuard)(tokenId, "string", "tokenId");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emblem-vault-sdk",
3
- "version": "2.11.0",
3
+ "version": "2.13.0",
4
4
  "description": "Emblem Vault Software Development Kit",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -41,12 +41,15 @@
41
41
  "dependencies": {
42
42
  "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3",
43
43
  "@ethersproject/bignumber": "^5.7.0",
44
+ "@noble/curves": "^1.4.2",
45
+ "@noble/hashes": "^1.4.0",
44
46
  "@toruslabs/fetch-node-details": "^8.0.0",
45
47
  "@toruslabs/torus.js": "^6.2.0",
46
48
  "bchaddrjs": "^0.5.2",
47
49
  "bip32": "^4.0.0",
48
50
  "bip39": "^3.1.0",
49
51
  "bitcoinjs-lib": "^6.1.5",
52
+ "bs58": "^4.0.1",
50
53
  "bitcore-mnemonic": "^10.0.23",
51
54
  "crypto-js": "^4.2.0",
52
55
  "ethereumjs-util": "^7.1.5",
package/src/abi/abi.json CHANGED
@@ -78,18 +78,18 @@
78
78
  "inputs": [
79
79
  {
80
80
  "internalType": "address",
81
- "name": "_nftAddress",
81
+ "name": "",
82
82
  "type": "address"
83
83
  },
84
84
  {
85
85
  "internalType": "uint256",
86
- "name": "tokenId",
86
+ "name": "",
87
87
  "type": "uint256"
88
88
  }
89
89
  ],
90
90
  "name": "claim",
91
91
  "outputs": [],
92
- "stateMutability": "nonpayable",
92
+ "stateMutability": "pure",
93
93
  "type": "function"
94
94
  },
95
95
  {
@@ -144,6 +144,82 @@
144
144
  "outputs": [],
145
145
  "stateMutability": "payable",
146
146
  "type": "function"
147
+ },
148
+ {
149
+ "inputs": [
150
+ {
151
+ "internalType": "address",
152
+ "name": "_nftAddress",
153
+ "type": "address"
154
+ },
155
+ {
156
+ "internalType": "uint256",
157
+ "name": "tokenId",
158
+ "type": "uint256"
159
+ },
160
+ {
161
+ "internalType": "address",
162
+ "name": "_payment",
163
+ "type": "address"
164
+ },
165
+ {
166
+ "internalType": "uint256",
167
+ "name": "_price",
168
+ "type": "uint256"
169
+ },
170
+ {
171
+ "internalType": "uint256",
172
+ "name": "_nonce",
173
+ "type": "uint256"
174
+ },
175
+ {
176
+ "internalType": "bytes",
177
+ "name": "_signature",
178
+ "type": "bytes"
179
+ }
180
+ ],
181
+ "name": "claimWithSignedPrice",
182
+ "outputs": [],
183
+ "stateMutability": "payable",
184
+ "type": "function"
185
+ },
186
+ {
187
+ "inputs": [
188
+ {
189
+ "internalType": "address[]",
190
+ "name": "nftAddresses",
191
+ "type": "address[]"
192
+ },
193
+ {
194
+ "internalType": "uint256[]",
195
+ "name": "tokenIds",
196
+ "type": "uint256[]"
197
+ },
198
+ {
199
+ "internalType": "address",
200
+ "name": "_payment",
201
+ "type": "address"
202
+ },
203
+ {
204
+ "internalType": "uint256",
205
+ "name": "_price",
206
+ "type": "uint256"
207
+ },
208
+ {
209
+ "internalType": "uint256",
210
+ "name": "_nonce",
211
+ "type": "uint256"
212
+ },
213
+ {
214
+ "internalType": "bytes",
215
+ "name": "_signature",
216
+ "type": "bytes"
217
+ }
218
+ ],
219
+ "name": "batchClaimWithSignedPrice",
220
+ "outputs": [],
221
+ "stateMutability": "payable",
222
+ "type": "function"
147
223
  }
148
224
  ],
149
225
  "legacy": [
package/src/derive.ts CHANGED
@@ -3,9 +3,61 @@ import * as bip39 from "bip39";
3
3
  // import bitcoin from "bitcoinjs-lib";
4
4
 
5
5
  import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'
6
+ import { hmac } from "@noble/hashes/hmac";
7
+ import { sha512 } from "@noble/hashes/sha512";
8
+ import { ed25519 } from "@noble/curves/ed25519";
9
+ // bs58@4 ships no type declarations; require it (CJS) to avoid TS7016.
10
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
11
+ const bs58: { encode: (b: Uint8Array) => string; decode: (s: string) => Uint8Array } = require("bs58");
6
12
 
7
13
  const bip32 = BIP32Factory(ecc);
8
14
 
15
+ // --- Solana (ed25519 SLIP-0010) HD derivation ---
16
+ // Solana uses ed25519, NOT secp256k1, so bip32/bitcoinjs can't derive it. We do the
17
+ // SLIP-0010 ed25519 derivation via @noble (browser-safe, already a dependency; NOT
18
+ // @solana/web3.js — that heavy dep is the conflict that got SOL derivation disabled).
19
+ // Path m/44'/501'/0'/0' (all hardened) is the Phantom/Solflare standard. This matches
20
+ // the serverless vault-creation derivation byte-for-byte, so a claimed vault's SOL key
21
+ // re-derives to the stored address.
22
+ const SOLANA_PATH = "m/44'/501'/0'/0'";
23
+
24
+ function slip10DeriveEd25519Seed(pathStr: string, seed: Uint8Array): Uint8Array {
25
+ let I = hmac(sha512, new TextEncoder().encode("ed25519 seed"), seed);
26
+ let key = I.slice(0, 32);
27
+ let chain = I.slice(32);
28
+ for (const seg of pathStr.split("/").slice(1)) {
29
+ const idx = ((parseInt(seg, 10) | 0x80000000) >>> 0); // all segments hardened
30
+ const data = new Uint8Array(1 + 32 + 4);
31
+ data[0] = 0;
32
+ data.set(key, 1);
33
+ data[33] = (idx >>> 24) & 255;
34
+ data[34] = (idx >>> 16) & 255;
35
+ data[35] = (idx >>> 8) & 255;
36
+ data[36] = idx & 255;
37
+ I = hmac(sha512, chain, data);
38
+ key = I.slice(0, 32);
39
+ chain = I.slice(32);
40
+ }
41
+ return key; // 32-byte ed25519 seed
42
+ }
43
+
44
+ // Derive the Solana address + Phantom-importable secret key from a vault mnemonic.
45
+ // Returns { address (base58 pubkey), secretKey (base58 of the 64-byte secret), path, coin }.
46
+ export const deriveSolanaFromMnemonic = (phrase: string) => {
47
+ const seed = bip39.mnemonicToSeedSync(phrase);
48
+ const priv = slip10DeriveEd25519Seed(SOLANA_PATH, new Uint8Array(seed));
49
+ const pub = ed25519.getPublicKey(priv);
50
+ const secret = new Uint8Array(64);
51
+ secret.set(priv, 0);
52
+ secret.set(pub, 32);
53
+ return {
54
+ address: bs58.encode(pub),
55
+ secretKey: bs58.encode(secret), // 64-byte secret, base58 — import into Phantom/Solflare
56
+ path: SOLANA_PATH,
57
+ coin: "SOL",
58
+ };
59
+ };
60
+
9
61
  // let mainnet: any = {"messagePrefix":"\u0018Bitcoin Signed Message:\n","bech32":"bc","bip32":{"public":76067358,"private":76066276},"pubKeyHash":0,"scriptHash":5,"wif":128}
10
62
  declare global {
11
63
  interface Window {