@campnetwork/origin 1.3.0-alpha.5 → 1.3.0-alpha.7

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.
@@ -320,9 +320,13 @@ interface RaiseDisputeSmartResult {
320
320
  }
321
321
  /**
322
322
  * Raises a dispute with automatic evidence upload to IPFS.
323
- * Uploads evidence JSON to IPFS, hashes the CID to bytes32 for on-chain storage,
323
+ * Uploads evidence JSON to IPFS, encodes the CID to bytes32 for on-chain storage,
324
324
  * and calls raiseDispute.
325
325
  *
326
+ * The CID is encoded by stripping the 0x1220 multihash prefix from CIDv0,
327
+ * leaving only the 32-byte SHA-256 digest. This encoding is reversible,
328
+ * allowing the original CID to be reconstructed from the on-chain data.
329
+ *
326
330
  * @param targetIpId The token ID of the IP NFT to dispute.
327
331
  * @param evidence The evidence JSON object to upload to IPFS.
328
332
  * @param disputeTag A tag identifying the type of dispute.
@@ -336,13 +340,11 @@ interface RaiseDisputeSmartResult {
336
340
  * "0x696e6672696e67656d656e74..." // dispute tag
337
341
  * );
338
342
  *
339
- * // Store the CID for evidence retrieval
343
+ * // The CID can be recovered from evidenceHash using decodeCidFromBytes32
340
344
  * console.log("Evidence CID:", result.evidenceCid);
341
345
  *
342
- * // Fetch evidence later via IPFS gateway
346
+ * // Fetch evidence via IPFS gateway
343
347
  * // https://ipfs.io/ipfs/{result.evidenceCid}
344
- *
345
- * // Verify evidence hash matches on-chain: keccak256(toHex(evidenceCid)) === evidenceHash
346
348
  * ```
347
349
  */
348
350
  declare function raiseDisputeSmart(this: Origin, targetIpId: bigint, evidence: Record<string, any>, disputeTag: Hex): Promise<RaiseDisputeSmartResult>;
@@ -362,6 +364,42 @@ declare function raiseDisputeSmart(this: Origin, targetIpId: bigint, evidence: R
362
364
  */
363
365
  declare function disputeAssertion(this: Origin, disputeId: bigint, counterEvidenceHash: Hex): Promise<any>;
364
366
 
367
+ interface DisputeAssertionSmartResult {
368
+ transactionResult: any;
369
+ counterEvidenceCid: string;
370
+ counterEvidenceHash: Hex;
371
+ }
372
+ /**
373
+ * Asserts a dispute with automatic counter-evidence upload to IPFS.
374
+ * Uploads counter-evidence JSON to IPFS, encodes the CID to bytes32 for on-chain storage,
375
+ * and calls disputeAssertion.
376
+ *
377
+ * The CID is encoded by stripping the 0x1220 multihash prefix from CIDv0,
378
+ * leaving only the 32-byte SHA-256 digest. This encoding is reversible,
379
+ * allowing the original CID to be reconstructed from the on-chain data.
380
+ *
381
+ * Must be called by the owner of the disputed IP within the cooldown period.
382
+ *
383
+ * @param disputeId The ID of the dispute to assert.
384
+ * @param counterEvidence The counter-evidence JSON object to upload to IPFS.
385
+ * @returns A promise that resolves with the transaction result, IPFS CID, and counter-evidence hash.
386
+ *
387
+ * @example
388
+ * ```typescript
389
+ * const result = await origin.disputeAssertionSmart(
390
+ * 1n,
391
+ * { description: "This is my original work", proofUrl: "https://..." }
392
+ * );
393
+ *
394
+ * // The CID can be recovered from counterEvidenceHash using decodeCidFromBytes32
395
+ * console.log("Counter-evidence CID:", result.counterEvidenceCid);
396
+ *
397
+ * // Fetch counter-evidence via IPFS gateway
398
+ * // https://ipfs.io/ipfs/{result.counterEvidenceCid}
399
+ * ```
400
+ */
401
+ declare function disputeAssertionSmart(this: Origin, disputeId: bigint, counterEvidence: Record<string, any>): Promise<DisputeAssertionSmartResult>;
402
+
365
403
  /**
366
404
  * Votes on a dispute as a CAMP token staker.
367
405
  * Only users who staked before the dispute was raised can vote.
@@ -541,6 +579,31 @@ interface DisputeProgress {
541
579
  */
542
580
  declare function getDisputeProgress(this: Origin, disputeId: bigint): Promise<DisputeProgress>;
543
581
 
582
+ interface DisputeRequirements {
583
+ bondAmount: bigint;
584
+ protocolFee: bigint;
585
+ totalRequired: bigint;
586
+ tokenAddress: Address;
587
+ isNativeToken: boolean;
588
+ userBalance: bigint;
589
+ hasSufficientBalance: boolean;
590
+ }
591
+ /**
592
+ * Gets the requirements for raising a dispute, including balance check.
593
+ *
594
+ * @param userAddress The address to check balance for.
595
+ * @returns A promise that resolves with the dispute requirements.
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * const requirements = await origin.getDisputeRequirements(walletAddress);
600
+ * if (!requirements.hasSufficientBalance) {
601
+ * console.log(`Need ${requirements.totalRequired} but only have ${requirements.userBalance}`);
602
+ * }
603
+ * ```
604
+ */
605
+ declare function getDisputeRequirements(this: Origin, userAddress: Address): Promise<DisputeRequirements>;
606
+
544
607
  /**
545
608
  * Fractionalizes an IP NFT into fungible ERC20 tokens.
546
609
  * The NFT is transferred to the fractionalizer contract and a new ERC20 token is created.
@@ -902,6 +965,7 @@ declare class Origin {
902
965
  raiseDispute: typeof raiseDispute;
903
966
  raiseDisputeSmart: typeof raiseDisputeSmart;
904
967
  disputeAssertion: typeof disputeAssertion;
968
+ disputeAssertionSmart: typeof disputeAssertionSmart;
905
969
  voteOnDispute: typeof voteOnDispute;
906
970
  resolveDispute: typeof resolveDispute;
907
971
  cancelDispute: typeof cancelDispute;
@@ -909,6 +973,7 @@ declare class Origin {
909
973
  getDispute: typeof getDispute;
910
974
  canVoteOnDispute: typeof canVoteOnDispute;
911
975
  getDisputeProgress: typeof getDisputeProgress;
976
+ getDisputeRequirements: typeof getDisputeRequirements;
912
977
  fractionalize: typeof fractionalize;
913
978
  redeem: typeof redeem;
914
979
  getTokenForNFT: typeof getTokenForNFT;
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import React, { createContext, useState, useContext, useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';
3
- import { custom, createWalletClient, createPublicClient, http, encodeFunctionData, checksumAddress, zeroAddress, keccak256, toBytes, toHex, erc20Abi, getAbiItem, formatEther, formatUnits, parseEther } from 'viem';
3
+ import { custom, createWalletClient, createPublicClient, http, encodeFunctionData, checksumAddress, zeroAddress, keccak256, toBytes, erc20Abi, getAbiItem, formatEther, formatUnits, parseEther } from 'viem';
4
4
  import { toAccount } from 'viem/accounts';
5
5
  import { createSiweMessage } from 'viem/siwe';
6
6
  import axios from 'axios';
@@ -6845,11 +6845,155 @@ function raiseDispute(targetIpId, evidenceHash, disputeTag) {
6845
6845
  });
6846
6846
  }
6847
6847
 
6848
+ // base-x encoding / decoding
6849
+ // Copyright (c) 2018 base-x contributors
6850
+ // Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp)
6851
+ // Distributed under the MIT software license, see the accompanying
6852
+ // file LICENSE or http://www.opensource.org/licenses/mit-license.php.
6853
+ function base (ALPHABET) {
6854
+ if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') }
6855
+ const BASE_MAP = new Uint8Array(256);
6856
+ for (let j = 0; j < BASE_MAP.length; j++) {
6857
+ BASE_MAP[j] = 255;
6858
+ }
6859
+ for (let i = 0; i < ALPHABET.length; i++) {
6860
+ const x = ALPHABET.charAt(i);
6861
+ const xc = x.charCodeAt(0);
6862
+ if (BASE_MAP[xc] !== 255) { throw new TypeError(x + ' is ambiguous') }
6863
+ BASE_MAP[xc] = i;
6864
+ }
6865
+ const BASE = ALPHABET.length;
6866
+ const LEADER = ALPHABET.charAt(0);
6867
+ const FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up
6868
+ const iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up
6869
+ function encode (source) {
6870
+ // eslint-disable-next-line no-empty
6871
+ if (source instanceof Uint8Array) ; else if (ArrayBuffer.isView(source)) {
6872
+ source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
6873
+ } else if (Array.isArray(source)) {
6874
+ source = Uint8Array.from(source);
6875
+ }
6876
+ if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') }
6877
+ if (source.length === 0) { return '' }
6878
+ // Skip & count leading zeroes.
6879
+ let zeroes = 0;
6880
+ let length = 0;
6881
+ let pbegin = 0;
6882
+ const pend = source.length;
6883
+ while (pbegin !== pend && source[pbegin] === 0) {
6884
+ pbegin++;
6885
+ zeroes++;
6886
+ }
6887
+ // Allocate enough space in big-endian base58 representation.
6888
+ const size = ((pend - pbegin) * iFACTOR + 1) >>> 0;
6889
+ const b58 = new Uint8Array(size);
6890
+ // Process the bytes.
6891
+ while (pbegin !== pend) {
6892
+ let carry = source[pbegin];
6893
+ // Apply "b58 = b58 * 256 + ch".
6894
+ let i = 0;
6895
+ for (let it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) {
6896
+ carry += (256 * b58[it1]) >>> 0;
6897
+ b58[it1] = (carry % BASE) >>> 0;
6898
+ carry = (carry / BASE) >>> 0;
6899
+ }
6900
+ if (carry !== 0) { throw new Error('Non-zero carry') }
6901
+ length = i;
6902
+ pbegin++;
6903
+ }
6904
+ // Skip leading zeroes in base58 result.
6905
+ let it2 = size - length;
6906
+ while (it2 !== size && b58[it2] === 0) {
6907
+ it2++;
6908
+ }
6909
+ // Translate the result into a string.
6910
+ let str = LEADER.repeat(zeroes);
6911
+ for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]); }
6912
+ return str
6913
+ }
6914
+ function decodeUnsafe (source) {
6915
+ if (typeof source !== 'string') { throw new TypeError('Expected String') }
6916
+ if (source.length === 0) { return new Uint8Array() }
6917
+ let psz = 0;
6918
+ // Skip and count leading '1's.
6919
+ let zeroes = 0;
6920
+ let length = 0;
6921
+ while (source[psz] === LEADER) {
6922
+ zeroes++;
6923
+ psz++;
6924
+ }
6925
+ // Allocate enough space in big-endian base256 representation.
6926
+ const size = (((source.length - psz) * FACTOR) + 1) >>> 0; // log(58) / log(256), rounded up.
6927
+ const b256 = new Uint8Array(size);
6928
+ // Process the characters.
6929
+ while (psz < source.length) {
6930
+ // Find code of next character
6931
+ const charCode = source.charCodeAt(psz);
6932
+ // Base map can not be indexed using char code
6933
+ if (charCode > 255) { return }
6934
+ // Decode character
6935
+ let carry = BASE_MAP[charCode];
6936
+ // Invalid character
6937
+ if (carry === 255) { return }
6938
+ let i = 0;
6939
+ for (let it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) {
6940
+ carry += (BASE * b256[it3]) >>> 0;
6941
+ b256[it3] = (carry % 256) >>> 0;
6942
+ carry = (carry / 256) >>> 0;
6943
+ }
6944
+ if (carry !== 0) { throw new Error('Non-zero carry') }
6945
+ length = i;
6946
+ psz++;
6947
+ }
6948
+ // Skip leading zeroes in b256.
6949
+ let it4 = size - length;
6950
+ while (it4 !== size && b256[it4] === 0) {
6951
+ it4++;
6952
+ }
6953
+ const vch = new Uint8Array(zeroes + (size - it4));
6954
+ let j = zeroes;
6955
+ while (it4 !== size) {
6956
+ vch[j++] = b256[it4++];
6957
+ }
6958
+ return vch
6959
+ }
6960
+ function decode (string) {
6961
+ const buffer = decodeUnsafe(string);
6962
+ if (buffer) { return buffer }
6963
+ throw new Error('Non-base' + BASE + ' character')
6964
+ }
6965
+ return {
6966
+ encode,
6967
+ decodeUnsafe,
6968
+ decode
6969
+ }
6970
+ }
6971
+
6972
+ var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
6973
+ var bs58 = base(ALPHABET);
6974
+
6975
+ /**
6976
+ * Encode CIDv0 to bytes32 by stripping the 0x1220 multihash prefix.
6977
+ * Only works with CIDv0 (SHA-256 hash, starts with "Qm").
6978
+ */
6979
+ function encodeCidToBytes32(cid) {
6980
+ const decoded = bs58.decode(cid);
6981
+ if (decoded[0] !== 0x12 || decoded[1] !== 0x20 || decoded.length !== 34) {
6982
+ throw new Error("Only CIDv0 with SHA-256 is supported");
6983
+ }
6984
+ // Return only the 32-byte digest (skip 0x1220 prefix)
6985
+ return `0x${Buffer.from(decoded.slice(2)).toString("hex")}`;
6986
+ }
6987
+
6848
6988
  /**
6849
6989
  * Raises a dispute with automatic evidence upload to IPFS.
6850
- * Uploads evidence JSON to IPFS, hashes the CID to bytes32 for on-chain storage,
6990
+ * Uploads evidence JSON to IPFS, encodes the CID to bytes32 for on-chain storage,
6851
6991
  * and calls raiseDispute.
6852
6992
  *
6993
+ * The CID is encoded by stripping the 0x1220 multihash prefix from CIDv0,
6994
+ * leaving only the 32-byte SHA-256 digest. This encoding is reversible,
6995
+ * allowing the original CID to be reconstructed from the on-chain data.
6996
+ *
6853
6997
  * @param targetIpId The token ID of the IP NFT to dispute.
6854
6998
  * @param evidence The evidence JSON object to upload to IPFS.
6855
6999
  * @param disputeTag A tag identifying the type of dispute.
@@ -6863,19 +7007,17 @@ function raiseDispute(targetIpId, evidenceHash, disputeTag) {
6863
7007
  * "0x696e6672696e67656d656e74..." // dispute tag
6864
7008
  * );
6865
7009
  *
6866
- * // Store the CID for evidence retrieval
7010
+ * // The CID can be recovered from evidenceHash using decodeCidFromBytes32
6867
7011
  * console.log("Evidence CID:", result.evidenceCid);
6868
7012
  *
6869
- * // Fetch evidence later via IPFS gateway
7013
+ * // Fetch evidence via IPFS gateway
6870
7014
  * // https://ipfs.io/ipfs/{result.evidenceCid}
6871
- *
6872
- * // Verify evidence hash matches on-chain: keccak256(toHex(evidenceCid)) === evidenceHash
6873
7015
  * ```
6874
7016
  */
6875
7017
  function raiseDisputeSmart(targetIpId, evidence, disputeTag) {
6876
7018
  return __awaiter(this, void 0, void 0, function* () {
6877
7019
  const cid = yield this.uploadJSONToIPFS(evidence);
6878
- const evidenceHash = keccak256(toHex(cid));
7020
+ const evidenceHash = encodeCidToBytes32(cid);
6879
7021
  const transactionResult = yield this.raiseDispute(targetIpId, evidenceHash, disputeTag);
6880
7022
  return {
6881
7023
  transactionResult,
@@ -6904,6 +7046,48 @@ function disputeAssertion(disputeId, counterEvidenceHash) {
6904
7046
  });
6905
7047
  }
6906
7048
 
7049
+ /**
7050
+ * Asserts a dispute with automatic counter-evidence upload to IPFS.
7051
+ * Uploads counter-evidence JSON to IPFS, encodes the CID to bytes32 for on-chain storage,
7052
+ * and calls disputeAssertion.
7053
+ *
7054
+ * The CID is encoded by stripping the 0x1220 multihash prefix from CIDv0,
7055
+ * leaving only the 32-byte SHA-256 digest. This encoding is reversible,
7056
+ * allowing the original CID to be reconstructed from the on-chain data.
7057
+ *
7058
+ * Must be called by the owner of the disputed IP within the cooldown period.
7059
+ *
7060
+ * @param disputeId The ID of the dispute to assert.
7061
+ * @param counterEvidence The counter-evidence JSON object to upload to IPFS.
7062
+ * @returns A promise that resolves with the transaction result, IPFS CID, and counter-evidence hash.
7063
+ *
7064
+ * @example
7065
+ * ```typescript
7066
+ * const result = await origin.disputeAssertionSmart(
7067
+ * 1n,
7068
+ * { description: "This is my original work", proofUrl: "https://..." }
7069
+ * );
7070
+ *
7071
+ * // The CID can be recovered from counterEvidenceHash using decodeCidFromBytes32
7072
+ * console.log("Counter-evidence CID:", result.counterEvidenceCid);
7073
+ *
7074
+ * // Fetch counter-evidence via IPFS gateway
7075
+ * // https://ipfs.io/ipfs/{result.counterEvidenceCid}
7076
+ * ```
7077
+ */
7078
+ function disputeAssertionSmart(disputeId, counterEvidence) {
7079
+ return __awaiter(this, void 0, void 0, function* () {
7080
+ const cid = yield this.uploadJSONToIPFS(counterEvidence);
7081
+ const counterEvidenceHash = encodeCidToBytes32(cid);
7082
+ const transactionResult = yield this.disputeAssertion(disputeId, counterEvidenceHash);
7083
+ return {
7084
+ transactionResult,
7085
+ counterEvidenceCid: cid,
7086
+ counterEvidenceHash,
7087
+ };
7088
+ });
7089
+ }
7090
+
6907
7091
  /**
6908
7092
  * Votes on a dispute as a CAMP token staker.
6909
7093
  * Only users who staked before the dispute was raised can vote.
@@ -7301,6 +7485,71 @@ function getDisputeProgress(disputeId) {
7301
7485
  });
7302
7486
  }
7303
7487
 
7488
+ /**
7489
+ * Gets the requirements for raising a dispute, including balance check.
7490
+ *
7491
+ * @param userAddress The address to check balance for.
7492
+ * @returns A promise that resolves with the dispute requirements.
7493
+ *
7494
+ * @example
7495
+ * ```typescript
7496
+ * const requirements = await origin.getDisputeRequirements(walletAddress);
7497
+ * if (!requirements.hasSufficientBalance) {
7498
+ * console.log(`Need ${requirements.totalRequired} but only have ${requirements.userBalance}`);
7499
+ * }
7500
+ * ```
7501
+ */
7502
+ function getDisputeRequirements(userAddress) {
7503
+ return __awaiter(this, void 0, void 0, function* () {
7504
+ const publicClient = getPublicClient();
7505
+ const disputeContractAddress = this.environment
7506
+ .DISPUTE_CONTRACT_ADDRESS;
7507
+ const disputeAbi = this.environment.DISPUTE_ABI;
7508
+ // Get dispute parameters from the contract
7509
+ const [bondAmount, protocolFee, tokenAddress] = yield Promise.all([
7510
+ publicClient.readContract({
7511
+ address: disputeContractAddress,
7512
+ abi: disputeAbi,
7513
+ functionName: "disputeBond",
7514
+ }),
7515
+ publicClient.readContract({
7516
+ address: disputeContractAddress,
7517
+ abi: disputeAbi,
7518
+ functionName: "protocolDisputeFee",
7519
+ }),
7520
+ publicClient.readContract({
7521
+ address: disputeContractAddress,
7522
+ abi: disputeAbi,
7523
+ functionName: "disputeToken",
7524
+ }),
7525
+ ]);
7526
+ const isNativeToken = tokenAddress === zeroAddress;
7527
+ const totalRequired = bondAmount + protocolFee;
7528
+ // Get user's balance
7529
+ let userBalance;
7530
+ if (isNativeToken) {
7531
+ userBalance = yield publicClient.getBalance({ address: userAddress });
7532
+ }
7533
+ else {
7534
+ userBalance = (yield publicClient.readContract({
7535
+ address: tokenAddress,
7536
+ abi: erc20Abi,
7537
+ functionName: "balanceOf",
7538
+ args: [userAddress],
7539
+ }));
7540
+ }
7541
+ return {
7542
+ bondAmount,
7543
+ protocolFee,
7544
+ totalRequired,
7545
+ tokenAddress,
7546
+ isNativeToken,
7547
+ userBalance,
7548
+ hasSufficientBalance: userBalance >= totalRequired,
7549
+ };
7550
+ });
7551
+ }
7552
+
7304
7553
  /**
7305
7554
  * Fractionalizes an IP NFT into fungible ERC20 tokens.
7306
7555
  * The NFT is transferred to the fractionalizer contract and a new ERC20 token is created.
@@ -7936,6 +8185,7 @@ class Origin {
7936
8185
  this.raiseDispute = raiseDispute.bind(this);
7937
8186
  this.raiseDisputeSmart = raiseDisputeSmart.bind(this);
7938
8187
  this.disputeAssertion = disputeAssertion.bind(this);
8188
+ this.disputeAssertionSmart = disputeAssertionSmart.bind(this);
7939
8189
  this.voteOnDispute = voteOnDispute.bind(this);
7940
8190
  this.resolveDispute = resolveDispute.bind(this);
7941
8191
  this.cancelDispute = cancelDispute.bind(this);
@@ -7943,6 +8193,7 @@ class Origin {
7943
8193
  this.getDispute = getDispute.bind(this);
7944
8194
  this.canVoteOnDispute = canVoteOnDispute.bind(this);
7945
8195
  this.getDisputeProgress = getDisputeProgress.bind(this);
8196
+ this.getDisputeRequirements = getDisputeRequirements.bind(this);
7946
8197
  // Fractionalizer module methods
7947
8198
  this.fractionalize = fractionalize.bind(this);
7948
8199
  this.redeem = redeem.bind(this);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campnetwork/origin",
3
- "version": "1.3.0-alpha.5",
3
+ "version": "1.3.0-alpha.7",
4
4
  "main": "dist/core.cjs",
5
5
  "exports": {
6
6
  ".": {
@@ -41,6 +41,7 @@
41
41
  "@tanstack/react-query": "^5",
42
42
  "@walletconnect/ethereum-provider": "^2.17.2",
43
43
  "axios": "^1.7.7",
44
+ "bs58": "^6.0.0",
44
45
  "viem": "^2.21.37",
45
46
  "wagmi": "^2.12.33"
46
47
  },