UNPKG

@hapi.one/solana-client

Version:

Client library for Solana smart contract for #HAPI

1,310 lines (1,290 loc) 57.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var web3_js = require('@solana/web3.js'); var borsh = require('borsh'); var BN = require('bn.js'); var bs58 = require('bs58'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var BN__default = /*#__PURE__*/_interopDefaultLegacy(BN); class u8 extends BN__default["default"] { constructor(...args) { super(...args); Object.setPrototypeOf(this, u8.prototype); } /** * Convert to Buffer representation */ toBuffer() { return bnToBuffer(this, u8.size); } /** * Construct a Numberu8 from Buffer representation */ static fromBuffer(buffer) { if (buffer.length !== u8.size) { throw new Error(`Invalid buffer length: ${buffer.length}`); } return new BN__default["default"]([...buffer] .reverse() .map((i) => `00${i.toString(4)}`.slice(-2)) .join(""), 16); } } u8.size = 1; class u32 extends BN__default["default"] { constructor(...args) { super(...args); Object.setPrototypeOf(this, u32.prototype); } /** * Convert to Buffer representation */ toBuffer() { return bnToBuffer(this, u32.size); } /** * Construct a Numberu32 from Buffer representation */ static fromBuffer(buffer) { if (buffer.length !== u32.size) { throw new Error(`Invalid buffer length: ${buffer.length}`); } return new BN__default["default"]([...buffer] .reverse() .map((i) => `00${i.toString(16)}`.slice(-2)) .join(""), 16); } } u32.size = 4; class u64 extends BN__default["default"] { constructor(...args) { super(...args); Object.setPrototypeOf(this, u64.prototype); } /** * Convert to Buffer representation */ toBuffer() { return bnToBuffer(this, u64.size); } /** * Construct a Numberu64 from Buffer representation */ static fromBuffer(buffer) { if (buffer.length !== u64.size) { throw new Error(`Invalid buffer length: ${buffer.length}`); } return new BN__default["default"]([...buffer] .reverse() .map((i) => `00${i.toString(16)}`.slice(-2)) .join(""), 16); } } u64.size = 8; function bnToBuffer(bn, size) { const a = bn.toArray().reverse(); const b = Buffer.from(a); if (b.length === size) { return b; } if (b.length > size) { throw new Error(`Buffer too large (size: ${size})`); } const zeroPad = Buffer.alloc(size); b.copy(zeroPad); return zeroPad; } function base58ToPublicKey(address) { const buffer = bs58.decode(address); return new web3_js.PublicKey(buffer); } // This enum must match the order of a smart contract enum exports.HapiInstruction = void 0; (function (HapiInstruction) { HapiInstruction[HapiInstruction["CreateCommunity"] = 0] = "CreateCommunity"; HapiInstruction[HapiInstruction["UpdateCommunity"] = 1] = "UpdateCommunity"; HapiInstruction[HapiInstruction["CreateNetwork"] = 2] = "CreateNetwork"; HapiInstruction[HapiInstruction["UpdateNetwork"] = 3] = "UpdateNetwork"; HapiInstruction[HapiInstruction["CreateReporter"] = 4] = "CreateReporter"; HapiInstruction[HapiInstruction["UpdateReporter"] = 5] = "UpdateReporter"; HapiInstruction[HapiInstruction["CreateCase"] = 6] = "CreateCase"; HapiInstruction[HapiInstruction["UpdateCase"] = 7] = "UpdateCase"; HapiInstruction[HapiInstruction["CreateAddress"] = 8] = "CreateAddress"; HapiInstruction[HapiInstruction["UpdateAddress"] = 9] = "UpdateAddress"; })(exports.HapiInstruction || (exports.HapiInstruction = {})); // This enum must match the order of a smart contract enum exports.HapiAccountType = void 0; (function (HapiAccountType) { HapiAccountType[HapiAccountType["Uninitialized"] = 0] = "Uninitialized"; HapiAccountType[HapiAccountType["Community"] = 1] = "Community"; HapiAccountType[HapiAccountType["Network"] = 2] = "Network"; HapiAccountType[HapiAccountType["Reporter"] = 3] = "Reporter"; HapiAccountType[HapiAccountType["Case"] = 4] = "Case"; HapiAccountType[HapiAccountType["Address"] = 5] = "Address"; })(exports.HapiAccountType || (exports.HapiAccountType = {})); // This enum must match the order of a smart contract enum exports.ReporterType = void 0; (function (ReporterType) { ReporterType[ReporterType["Inactive"] = 0] = "Inactive"; ReporterType[ReporterType["Tracer"] = 1] = "Tracer"; ReporterType[ReporterType["Full"] = 2] = "Full"; ReporterType[ReporterType["Authority"] = 3] = "Authority"; })(exports.ReporterType || (exports.ReporterType = {})); // This enum must match the order of a smart contract enum exports.Category = void 0; (function (Category) { // Tier 0 /// Safe Category[Category["Safe"] = 0] = "Safe"; // Tier 1 - Low risk /// Wallet service - custodial or mixed wallets Category[Category["WalletService"] = 1] = "WalletService"; /// Merchant service Category[Category["MerchantService"] = 2] = "MerchantService"; /// Mining pool Category[Category["MiningPool"] = 4] = "MiningPool"; /// Exchange (Low Risk) - Exchange with high KYC standards Category[Category["LowRiskExchange"] = 8] = "LowRiskExchange"; // Tier 2 - Medium risk /// Exchange (Medium Risk) Category[Category["MediumRiskExchange"] = 16] = "MediumRiskExchange"; /// DeFi application Category[Category["DeFi"] = 32] = "DeFi"; /// OTC Broker Category[Category["OTCBroker"] = 64] = "OTCBroker"; /// Cryptocurrency ATM Category[Category["ATM"] = 128] = "ATM"; /// Gambling Category[Category["Gambling"] = 256] = "Gambling"; // Tier 3 - High risk /// Illicit organization Category[Category["IllicitOrganization"] = 512] = "IllicitOrganization"; /// Mixer Category[Category["Mixer"] = 1024] = "Mixer"; /// Darknet market or service Category[Category["DarknetService"] = 2048] = "DarknetService"; /// Scam Category[Category["Scam"] = 4096] = "Scam"; /// Ransomware Category[Category["Ransomware"] = 8192] = "Ransomware"; /// Theft - stolen funds Category[Category["Theft"] = 16384] = "Theft"; /// Counterfeit - fake assets that try to appear as something else Category[Category["Counterfeit"] = 32768] = "Counterfeit"; // Tier 4 - Severe risk /// Terrorist financing Category[Category["TerroristFinancing"] = 65536] = "TerroristFinancing"; /// Sanctions Category[Category["Sanctions"] = 131072] = "Sanctions"; /// Child abuse and porn materials Category[Category["ChildAbuse"] = 262144] = "ChildAbuse"; })(exports.Category || (exports.Category = {})); const Categories = [ exports.Category.Safe, exports.Category.WalletService, exports.Category.MerchantService, exports.Category.MiningPool, exports.Category.LowRiskExchange, exports.Category.MediumRiskExchange, exports.Category.DeFi, exports.Category.OTCBroker, exports.Category.ATM, exports.Category.Gambling, exports.Category.IllicitOrganization, exports.Category.Mixer, exports.Category.DarknetService, exports.Category.Scam, exports.Category.Ransomware, exports.Category.Theft, exports.Category.Counterfeit, exports.Category.TerroristFinancing, exports.Category.Sanctions, exports.Category.ChildAbuse, ]; exports.CaseStatus = void 0; (function (CaseStatus) { CaseStatus[CaseStatus["Open"] = 0] = "Open"; CaseStatus[CaseStatus["Closed"] = 1] = "Closed"; })(exports.CaseStatus || (exports.CaseStatus = {})); class CommunityState { constructor(object) { Object.assign(this, object); } } CommunityState.schema = new Map([ [ CommunityState, { kind: "struct", fields: [ ["account_type", "u8"], ["authority", [32]], ["next_case_id", "u64"], ["name", "string"], ], }, ], ]); CommunityState.size = 73; class Community { constructor(data) { /// HAPI account type this.accountType = exports.HapiAccountType.Community; if (data) { this.authority = data.authority; this.nextCaseId = data.nextCaseId; this.name = data.name; } } static async getAddress(programId, communityName) { return web3_js.PublicKey.findProgramAddress(Community.getAddressSeeds(communityName), programId); } static getAddressSeeds(communityName) { return [Buffer.from("community"), Buffer.from(communityName)]; } static fromState(state) { return new Community({ authority: new web3_js.PublicKey(state.authority), nextCaseId: new u64(state.next_case_id), name: state.name, }); } static deserialize(buffer) { return Community.fromState(borsh.deserializeUnchecked(CommunityState.schema, CommunityState, buffer)); } static async retrieve(programId, connection, communityName) { const [communityAddress] = await Community.getAddress(programId, communityName); const communityAccount = await connection.getAccountInfo(communityAddress); if (!communityAccount) { throw new Error(`Community not found: ${communityName} (${communityAddress})`); } return { data: Community.deserialize(communityAccount.data), account: communityAddress, }; } serialize() { const buf = Buffer.alloc(CommunityState.size); buf.set(borsh.serialize(CommunityState.schema, this.toState())); return buf; } toState() { return new CommunityState({ account_type: this.accountType, authority: this.authority.toBytes(), next_case_id: this.nextCaseId, name: this.name, }); } } Community.size = CommunityState.size; class NetworkState { constructor(object) { Object.assign(this, object); } } NetworkState.schema = new Map([ [ NetworkState, { kind: "struct", fields: [ ["account_type", "u8"], ["name", "string"], ], }, ], ]); NetworkState.size = 33; class Network { constructor(object) { /// HAPI account type this.accountType = exports.HapiAccountType.Network; if (object) { Object.assign(this, object); } } static async getAddress(programId, communityAddress, networkName) { return web3_js.PublicKey.findProgramAddress([ Buffer.from("network"), communityAddress.toBuffer(), Buffer.from(networkName), ], programId); } static fromState(state) { const network = new Network(); network.accountType = state.account_type; network.name = state.name; return network; } static deserialize(buffer) { return Network.fromState(borsh.deserializeUnchecked(NetworkState.schema, NetworkState, buffer)); } static async retrieve(programId, connection, communityName, networkName) { const [communityAddress] = await Community.getAddress(programId, communityName); const [networkAddress] = await Network.getAddress(programId, communityAddress, networkName); const account = await connection.getAccountInfo(networkAddress); if (!account) { throw new Error(`Network not found: "${networkName}" (${networkAddress}) in community "${communityName}" (${communityAddress})`); } return { data: Network.deserialize(account.data), account: networkAddress }; } serialize() { const buf = Buffer.alloc(NetworkState.size); buf.set(borsh.serialize(NetworkState.schema, this.toState())); return buf; } toState() { return new NetworkState({ account_type: this.accountType, name: this.name, }); } } Network.size = NetworkState.size; class AddressState { constructor(object) { Object.assign(this, object); } } AddressState.schema = new Map([ [ AddressState, { kind: "struct", fields: [ ["account_type", "u8"], ["risk", "u8"], ["case_id", "u64"], ["category", "u8"], ], }, ], ]); AddressState.size = 11; class Address { constructor(data) { /// HAPI account type this.accountType = exports.HapiAccountType.Address; if (data) { Object.assign(this, data); } } static async getAddress(programId, networkAddress, address) { return web3_js.PublicKey.findProgramAddress([Buffer.from("address"), networkAddress.toBuffer(), address.toBuffer()], programId); } static fromState(state) { return new Address({ accountType: state.account_type, risk: state.risk, caseId: state.case_id, category: Categories[state.category], }); } static deserialize(buffer) { return Address.fromState(borsh.deserializeUnchecked(AddressState.schema, AddressState, buffer)); } static async retrieve(programId, connection, communityName, networkName, address) { const [communityAddress] = await Community.getAddress(programId, communityName); const [networkAddress] = await Network.getAddress(programId, communityAddress, networkName); const [addressAddress] = await Address.getAddress(programId, networkAddress, address); const account = await connection.getAccountInfo(addressAddress); if (!account) { throw new Error(`Address not found: "${address}" in network "${networkName}" (${networkAddress}) in community "${communityName}" (${communityAddress})`); } return { data: Address.deserialize(account.data), account: addressAddress }; } serialize() { const buf = Buffer.alloc(AddressState.size); buf.set(borsh.serialize(AddressState.schema, this.toState())); return buf; } toState() { return new AddressState({ account_type: this.accountType, risk: this.risk, case_id: this.caseId, category: Categories.indexOf(this.category), }); } } class CaseState { constructor(object) { Object.assign(this, object); } } CaseState.schema = new Map([ [ CaseState, { kind: "struct", fields: [ ["account_type", "u8"], ["reporter_key", [32]], ["categories", "u32"], ["status", "u8"], ["name", "string"], ], }, ], ]); CaseState.size = 71; class Case { constructor(data) { /// HAPI account type this.accountType = exports.HapiAccountType.Case; if (data) { Object.assign(this, data); } } static async getAddress(programId, communityAddress, caseId) { return web3_js.PublicKey.findProgramAddress([Buffer.from("case"), communityAddress.toBuffer(), caseId.toBuffer()], programId); } static fromState(state) { return new Case({ accountType: state.account_type, name: state.name, reporterKey: new web3_js.PublicKey(state.reporter_key), status: state.status, categories: Categories.filter((category) => state.categories & category).sort(), }); } static deserialize(buffer) { return Case.fromState(borsh.deserializeUnchecked(CaseState.schema, CaseState, buffer)); } static async retrieve(programId, connection, communityName, caseId) { const [communityAddress] = await Community.getAddress(programId, communityName); const [caseAddress] = await Case.getAddress(programId, communityAddress, caseId); const account = await connection.getAccountInfo(caseAddress); if (!account) { throw new Error("Invalid case account provided"); } return { data: Case.deserialize(account.data), account: communityAddress }; } serialize() { const buf = Buffer.alloc(CaseState.size); buf.set(borsh.serialize(CaseState.schema, this.toState())); return buf; } toState() { return new CaseState({ account_type: this.accountType, name: this.name, reporter_key: this.reporterKey.toBytes(), status: this.status, categories: this.categories.reduce((acc, category) => { return acc | category; }, 0), }); } } class ReporterState { constructor(object) { Object.assign(this, object); } } ReporterState.schema = new Map([ [ ReporterState, { kind: "struct", fields: [ ["account_type", "u8"], ["reporter_type", "u8"], ["name", "string"], ], }, ], ]); ReporterState.size = 34; class Reporter { constructor(data) { /// HAPI account type this.accountType = exports.HapiAccountType.Reporter; if (data) { Object.assign(this, data); } } static async getAddress(programId, communityAddress, reporterPubkey) { return web3_js.PublicKey.findProgramAddress([ Buffer.from("reporter"), communityAddress.toBuffer(), reporterPubkey.toBuffer(), ], programId); } static fromState(state) { return new Reporter({ accountType: state.account_type, reporterType: state.reporter_type, name: state.name, }); } static deserialize(buffer) { return Reporter.fromState(borsh.deserializeUnchecked(ReporterState.schema, ReporterState, buffer)); } static async retrieve(programId, connection, communityName, reporterPubkey) { const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, reporterPubkey); const account = await connection.getAccountInfo(reporterAddress); if (!account) { throw new Error(`Reporter not found: "${reporterPubkey}" in community "${communityName}" (${communityAddress})`); } return { data: Reporter.deserialize(account.data), account: reporterAddress, }; } serialize() { const buf = Buffer.alloc(ReporterState.size); buf.set(borsh.serialize(ReporterState.schema, this.toState())); return buf; } toState() { return new ReporterState({ account_type: this.accountType, reporter_type: this.reporterType, name: this.name, }); } } Reporter.size = ReporterState.size; const HAPI_PROGRAM_ID = new web3_js.PublicKey("hapiScWyxeZy36fqXD5CcRUYFCUdid26jXaakAtcdZ7"); /** HAPI client to read entity data from Solana */ class ReaderClient { constructor(config) { if (typeof config.endpoint === "object") { this.connection = config.endpoint; } else { this.connection = new web3_js.Connection(config.endpoint, config.commitment); } this.communityName = config.communityName; this.programId = new web3_js.PublicKey(this.programId || HAPI_PROGRAM_ID); } ensureCommunityName(communityName) { if (!communityName) { communityName = this.communityName; } if (!communityName) { throw new Error("Community name not set"); } return communityName; } /** * Sets community name for context * @param communityName Community name * @returns Self */ switchCommunity(communityName) { this.communityName = communityName; return this; } /** * Fetch community info from blockchain * @param communityName (Optional) The name of the community to fetch (defaults to context) * @returns Community info **/ async getCommunity(communityName) { communityName = this.ensureCommunityName(communityName); const state = await Community.retrieve(this.programId, this.connection, communityName || this.communityName); return state; } /** * Fetch network info from blockchain * @param networkName The name of the network to fetch * @param communityName (Optional) The name of the community to fetch network from (defaults to context) * @returns Network info **/ async getNetwork(networkName, communityName) { communityName = this.ensureCommunityName(communityName); if (!networkName) { throw new Error("Network name not specified"); } const state = await Network.retrieve(this.programId, this.connection, communityName || this.communityName, networkName); return state; } /** * Fetch reporter info from blockchain * @param reporterPubkey Public key of the reporter to fetch * @param communityName (Optional) The name of the community to fetch reporter from (defaults to context) * @returns Reporter info **/ async getReporter(reporterPubkey, communityName) { communityName = this.ensureCommunityName(communityName); const state = await Reporter.retrieve(this.programId, this.connection, communityName || this.communityName, new web3_js.PublicKey(reporterPubkey)); return state; } /** * Fetch case info from blockchain * @param caseId ID of the case to fetch * @param communityName (Optional) The name of the community to fetch case from (defaults to context) * @returns Case info **/ async getCase(caseId, communityName) { communityName = this.ensureCommunityName(communityName); const state = await Case.retrieve(this.programId, this.connection, communityName || this.communityName, caseId); return state; } /** * Fetch address info from blockchain * @param address The address to fetch info for (string for Solana addresses, Buffer for others) * @param networkName The name of the network to which address belongs to * @param communityName (Optional) The name of the community to fetch case from (defaults to context) * @returns Address info **/ async getAddress(address, networkName, communityName) { communityName = this.ensureCommunityName(communityName); if (!networkName) { throw new Error("Network name not specified"); } const convertedAddress = address instanceof Buffer ? new web3_js.PublicKey(address) : base58ToPublicKey(address); const state = await Address.retrieve(this.programId, this.connection, communityName || this.communityName, networkName, convertedAddress); return state; } } function categoriesToBitmask(categories) { let bitmask = new u32(0); for (const category of categories) { bitmask = bitmask.or(new BN__default["default"](category)); } return bitmask; } function categoryToBinary(category) { return new u8(Categories.indexOf(category)); } const SYSTEM_RENT_KEYS = [ { pubkey: web3_js.SystemProgram.programId, isSigner: false, isWritable: false, }, { pubkey: web3_js.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, ]; const PROGRAM_SCHEMA = web3_js.SOLANA_SCHEMA; class CreateCommunityIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.CreateCommunity; } } PROGRAM_SCHEMA.set(CreateCommunityIx, { kind: "struct", fields: [ ["tag", "u8"], ["name", "string"], ], }); class UpdateCommunityIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.UpdateCommunity; } } PROGRAM_SCHEMA.set(UpdateCommunityIx, { kind: "struct", fields: [ ["tag", "u8"], ["name", "string"], ], }); class CreateNetworkIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.CreateNetwork; } } PROGRAM_SCHEMA.set(CreateNetworkIx, { kind: "struct", fields: [ ["tag", "u8"], ["name", "string"], ], }); class CreateReporterIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.CreateReporter; } } PROGRAM_SCHEMA.set(CreateReporterIx, { kind: "struct", fields: [ ["tag", "u8"], ["reporterType", "u8"], ["name", "string"], ], }); class UpdateReporterIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.UpdateReporter; } } PROGRAM_SCHEMA.set(UpdateReporterIx, { kind: "struct", fields: [ ["tag", "u8"], ["reporterType", "u8"], ["name", "string"], ], }); class CreateCaseIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.CreateCase; } } PROGRAM_SCHEMA.set(CreateCaseIx, { kind: "struct", fields: [ ["tag", "u8"], ["caseId", "u64"], ["categories", "u32"], ["status", "u8"], ["name", "string"], ], }); class UpdateCaseIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.UpdateCase; } } PROGRAM_SCHEMA.set(UpdateCaseIx, { kind: "struct", fields: [ ["tag", "u8"], ["categories", "u32"], ["status", "u8"], ], }); class CreateAddressIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.CreateAddress; } } PROGRAM_SCHEMA.set(CreateAddressIx, { kind: "struct", fields: [ ["tag", "u8"], ["address", [32]], ["risk", "u8"], ["caseId", "u64"], ["category", "u8"], ], }); class UpdateAddressIx extends web3_js.Struct { constructor() { super(...arguments); this.tag = exports.HapiInstruction.UpdateAddress; } } PROGRAM_SCHEMA.set(UpdateAddressIx, { kind: "struct", fields: [ ["tag", "u8"], ["risk", "u8"], ["caseId", "u64"], ["category", "u8"], ], }); async function createCaseInstruction({ programId, payer, communityName, caseId, caseName, status, categories, }) { if (Buffer.from(caseName).length > 28) { throw new Error("Case name length should not be over 28 bytes"); } if (status !== exports.CaseStatus.Open && status !== exports.CaseStatus.Closed) { throw new Error(`Unknown case status ${status}`); } categories.forEach((category) => { if (Categories.indexOf(category) < 0) { throw new Error(`Unknown category ${category}`); } }); const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, payer); const [caseAddress] = await Case.getAddress(programId, communityAddress, caseId); const ix = new CreateCaseIx({ caseId, status, name: caseName, categories: categoriesToBitmask(categories), }); const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: communityAddress, isSigner: false, isWritable: true }, { pubkey: reporterAddress, isSigner: false, isWritable: false }, { pubkey: caseAddress, isSigner: false, isWritable: true }, ...SYSTEM_RENT_KEYS, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } async function updateCaseInstruction({ programId, payer, communityName, caseId, status, categories, }) { categories.forEach((category) => { if (Categories.indexOf(category) < 0) { throw new Error(`Unknown category ${category}`); } }); if (status !== exports.CaseStatus.Open && status !== exports.CaseStatus.Closed) { throw new Error(`Unknown case status ${status}`); } const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, payer); const [caseAddress] = await Case.getAddress(programId, communityAddress, caseId); const ix = new UpdateCaseIx({ status, categories: categoriesToBitmask(categories), }); const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: communityAddress, isSigner: false, isWritable: false }, { pubkey: reporterAddress, isSigner: false, isWritable: false }, { pubkey: caseAddress, isSigner: false, isWritable: true }, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } async function createAddressInstruction({ programId, payer, communityName, networkName, address, caseId, risk, category, }) { if (Categories.indexOf(category) < 0) { throw new Error(`Unknown category: ${category}`); } if (risk < 0 || risk > 10) { throw new RangeError(`Risk must have a value between 0 and 10`); } const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, payer); const [networkAddress] = await Network.getAddress(programId, communityAddress, networkName); const [caseAddress] = await Case.getAddress(programId, communityAddress, caseId); const [addressAddress] = await Address.getAddress(programId, networkAddress, address); const ix = new CreateAddressIx({ address: address.toBytes(), risk, caseId, category: categoryToBinary(category), }); const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: communityAddress, isSigner: false, isWritable: true }, { pubkey: networkAddress, isSigner: false, isWritable: false }, { pubkey: reporterAddress, isSigner: false, isWritable: false }, { pubkey: caseAddress, isSigner: false, isWritable: false }, { pubkey: addressAddress, isSigner: false, isWritable: true }, ...SYSTEM_RENT_KEYS, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } async function updateAddressInstruction({ programId, payer, communityName, networkName, address, caseId, risk, category, }) { if (Categories.indexOf(category) < 0) { throw new Error(`Unknown category: ${category}`); } if (risk < 0 || risk > 10) { throw new RangeError(`Risk must have a value between 0 and 10`); } const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, payer); const [networkAddress] = await Network.getAddress(programId, communityAddress, networkName); const [caseAddress] = await Case.getAddress(programId, communityAddress, caseId); const [addressAddress] = await Address.getAddress(programId, networkAddress, address); const ix = new UpdateAddressIx({ risk, caseId, category: categoryToBinary(category), }); const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: communityAddress, isSigner: false, isWritable: false }, { pubkey: networkAddress, isSigner: false, isWritable: false }, { pubkey: reporterAddress, isSigner: false, isWritable: false }, { pubkey: caseAddress, isSigner: false, isWritable: false }, { pubkey: addressAddress, isSigner: false, isWritable: true }, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } /** HAPI client to operate reporter program functions on Solana */ class ReporterClient extends ReaderClient { constructor(config) { super(config); this.payer = config.payer; } get payerPublicKey() { return this.payer instanceof web3_js.Keypair ? this.payer.publicKey : this.payer; } get payerKeypair() { if (this.payer instanceof web3_js.Keypair) { return this.payer; } throw new Error(`Client is not initialized with payer secret key`); } /** * Create a case creation transaction that can be signed elsewhere * @param payer Public key of the payer account * @param communityName The name of the community to create * @param caseName The name of the case to create * @param categories An array of categories to assign to the case * @returns Transaction to sign **/ async createCaseTransaction(caseName, status, categories, communityName) { communityName = this.ensureCommunityName(communityName); const community = await Community.retrieve(this.programId, this.connection, communityName); const caseId = community.data.nextCaseId; const transaction = new web3_js.Transaction(); transaction.add(await createCaseInstruction({ programId: this.programId, payer: this.payerPublicKey, caseId, caseName, status, categories, communityName, })); return { transaction, caseId }; } /** * Create and sign a case creation transaction * @param payer Public key of the payer account * @param communityName The name of the community to create * @param caseName The name of the case to create * @param categories An array of categories to assign to the case * @returns Transaction hash, account address and entity data **/ async createCase(caseName, status, caseCategories, communityName) { communityName = this.ensureCommunityName(communityName); const { transaction, caseId } = await this.createCaseTransaction(caseName, status, caseCategories, communityName); const txHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.payerKeypair]); const { data, account } = await Case.retrieve(this.programId, this.connection, communityName, caseId); return { account, data, txHash, meta: { caseId } }; } /** * Create a case updating transaction that can be signed elsewhere * @param payer Public key of the payer account * @param communityName The name of the community to create * @param caseId The ID of the case to update * @param categories An array of categories to assign to the case * @returns Transaction to sign **/ async updateCaseTransaction(caseId, status, categories, communityName) { communityName = this.ensureCommunityName(communityName); const transaction = new web3_js.Transaction(); transaction.add(await updateCaseInstruction({ programId: this.programId, payer: this.payerPublicKey, communityName, caseId, status, categories, })); return { transaction }; } /** * Create and sign a case updating transaction * @param payer Public key of the payer account * @param communityName The name of the community to create * @param caseID The ID of the case to update * @param categories An array of categories to assign to the case * @returns Transaction hash, account address and entity data **/ async updateCase(caseId, status, caseCategories, communityName) { communityName = this.ensureCommunityName(communityName); // Make sure the community exists await Community.retrieve(this.programId, this.connection, communityName); // Make sure the case exists await Case.retrieve(this.programId, this.connection, communityName, caseId); const { transaction } = await this.updateCaseTransaction(caseId, status, caseCategories, communityName); const txHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.payerKeypair]); const { data, account } = await Case.retrieve(this.programId, this.connection, communityName, caseId); return { account, data, txHash }; } /** * Create an address creation transaction that can be signed elsewhere * @param payer Public key of the payer account * @param communityName The name of the community to create * @param networkName The name of the network of the address * @param address Public key of the address * @param caseId The ID of the case to assign to the address * @param category Category to assign to the address * @param risk Risk score to assign to the address (0 to 10) * @returns Transaction to sign **/ async createAddressTransaction(networkName, address, caseId, category, risk, communityName) { communityName = this.ensureCommunityName(communityName); // Make sure the community exists await Community.retrieve(this.programId, this.connection, communityName); // Make sure the network exists await Network.retrieve(this.programId, this.connection, communityName, networkName); // Make sure the case exists await Case.retrieve(this.programId, this.connection, communityName, caseId); const transaction = new web3_js.Transaction(); risk = parseInt(risk.toString()); if (risk < 0 || risk > 10) { throw new RangeError("risk should be an integer between 0 and 10"); } transaction.add(await createAddressInstruction({ programId: this.programId, payer: this.payerPublicKey, communityName, networkName, address, caseId, category, risk, })); return { transaction }; } /** * Create and sign a address creation transaction * @param payer Public key of the payer account * @param communityName The name of the community to create * @param networkName The name of the network of the address * @param address Public key of the address * @param caseId The ID of the case to assign to the address * @param category Category to assign to the address * @param risk Risk score to assign to the address (0 to 10) * @returns Transaction hash, account address and entity data **/ async createAddress(networkName, address, caseId, category, risk, communityName) { communityName = this.ensureCommunityName(communityName); const { transaction } = await this.createAddressTransaction(networkName, address, caseId, category, risk, communityName); const txHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.payerKeypair]); const { data, account } = await Address.retrieve(this.programId, this.connection, communityName, networkName, address); return { account, data, txHash }; } /** * Create an address updating transaction that can be signed elsewhere * @param payer Public key of the payer account * @param communityName The name of the community to create * @param networkName The name of the network of the address * @param address Public key of the address * @param caseId The ID of the case to assign to the address * @param category Category to assign to the address * @param risk Risk score to assign to the address (0 to 10) * @returns Transaction to sign **/ async updateAddressTransaction(networkName, address, caseId, category, risk, communityName) { communityName = this.ensureCommunityName(communityName); const transaction = new web3_js.Transaction(); transaction.add(await updateAddressInstruction({ programId: this.programId, payer: this.payerPublicKey, communityName, networkName, address, caseId, category, risk, })); return { transaction }; } /** * Create and sign a address updating transaction * @param payer Public key of the payer account * @param communityName The name of the community to create * @param networkName The name of the network of the address * @param address Public key of the address * @param caseId The ID of the case to assign to the address * @param category Category to assign to the address * @param risk Risk score to assign to the address (0 to 10) * @returns Transaction hash, account address and entity data **/ async updateAddress(networkName, address, caseId, category, risk, communityName) { communityName = this.ensureCommunityName(communityName); const { transaction } = await this.updateAddressTransaction(networkName, address, caseId, category, risk, communityName); const txHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.payerKeypair]); const { data, account } = await Address.retrieve(this.programId, this.connection, communityName, networkName, address); return { account, data, txHash }; } } async function createCommunityInstruction({ programId, payer, communityName, }) { if (Buffer.from(communityName).length > 28) { throw new Error("Community name length should not be over 28 bytes"); } const [communityAddress] = await Community.getAddress(programId, communityName); const ix = new CreateCommunityIx({ name: communityName }); const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: communityAddress, isSigner: false, isWritable: true }, ...SYSTEM_RENT_KEYS, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } async function createNetworkInstructions({ programId, payer, communityName, networkName, }) { if (Buffer.from(networkName).length > 28) { throw new Error("Network name length should not be over 28 bytes"); } const [communityAddress] = await Community.getAddress(programId, communityName); const [networkAddress] = await Network.getAddress(programId, communityAddress, networkName); const ix = new CreateNetworkIx({ name: networkName }); const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: networkAddress, isSigner: false, isWritable: true }, { pubkey: communityAddress, isSigner: false, isWritable: false }, ...SYSTEM_RENT_KEYS, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } async function createReporterInstructions({ programId, payer, communityName, reporterPubkey, reporterType, reporterName, }) { if (Buffer.from(reporterName).length > 28) { throw new Error("Reporter name length should not be over 28 bytes"); } const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, reporterPubkey); const ix = new CreateReporterIx({ name: reporterName, reporterType, }); const keys = [ { pubkey: payer, isSigner: true, isWritable: false }, { pubkey: communityAddress, isSigner: false, isWritable: true }, { pubkey: reporterPubkey, isSigner: false, isWritable: false }, { pubkey: reporterAddress, isSigner: false, isWritable: true }, ...SYSTEM_RENT_KEYS, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } async function updateReporterInstructions({ programId, payer, communityName, reporterPubkey, reporterType, reporterName, }) { if (Buffer.from(reporterName).length > 28) { throw new Error("Reporter name length should not be over 28 bytes"); } const [communityAddress] = await Community.getAddress(programId, communityName); const [reporterAddress] = await Reporter.getAddress(programId, communityAddress, reporterPubkey); const ix = new UpdateReporterIx({ name: reporterName, reporterType, }); const keys = [ { pubkey: payer, isSigner: true, isWritable: false }, { pubkey: communityAddress, isSigner: false, isWritable: true }, { pubkey: reporterPubkey, isSigner: false, isWritable: false }, { pubkey: reporterAddress, isSigner: false, isWritable: true }, ]; const instruction = new web3_js.TransactionInstruction({ keys, programId, data: ix.encode(), }); return instruction; } /** HAPI client to operate authority program functions on Solana */ class AuthorityClient extends ReaderClient { constructor(config) { super(config); this.payer = config.payer; } get payerPublicKey() { return this.payer instanceof web3_js.Keypair ? this.payer.publicKey : this.payer; } get payerKeypair() { if (this.payer instanceof web3_js.Keypair) { return this.payer; } throw new Error(`Client is not initialized with payer secret key`); } /** * Create a community creation transaction that can be signed elsewhere * @param payer Public key of the payer account * @param communityName The name of the community to create * @returns Transaction to sign **/ async createCommunityTransaction(communityName) { communityName = this.ensureCommunityName(communityName); const transaction = new web3_js.Transaction(); // Form a program instruction transaction.add(await createCommunityInstruction({ programId: this.programId, payer: this.payerPublicKey, communityName, })); return { transaction }; } /** * Create and sign a community creation transaction * @param payer Payer's key pair to sign the transaction * @param communityName The name of the community to create * @param authority (Optional) Public key of an authority of the community (defaults to payer public key) * @returns Transaction hash, account address and entity data **/ async createCommunity(communityName) { communityName = this.ensureCommunityName(communityName); const { transaction } = await this.createCommunityTransaction(communityName); const txHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.payerKeypair], { commitment: "confirmed" }); const { data, account } = await Community.retrieve(this.programId, this.connection, communityName); return { account, data, txHash }; } /** * Create a network creation transaction that can be signed elsewhere * @param payer Public key of the payer account (must be the community authority) * @param communityName The name of the community that the network should belong to * @param networkName The name of the network to create * @returns Transaction to sign **/ async createNetworkTransaction(networkName, communityName) { communityName = this.ensureCommunityName(communityName); const transaction = new web3_js.Transaction(); // Form a program instruction transaction.add(await createNetworkInstructions({ programId: this.programId, payer: this.payerPublicKey, communityName, networkName, })); return { transaction }; } /** * Create and sign a network creation transaction * @param payer Payer's key pair to sign the transaction * @param communityName The name of the community that the network should belong to * @param networkName The name of the network to create * @returns Transaction hash, account address and entity data **/ async createNetwork(networkName, communityName) { communityName = this.ensureCommunityName(communityName); const { transaction } = await this.createNetworkTransaction(networkName, communityName); const txHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.payerKeypair], { commitment: "confirmed" }); const { data, account } = await Network.retrieve(this.programId, this.connection, communityName, networkName); return { account, data, txHash }; } /** * Create a reporter creation transaction that can be signed elsewhere * @param payer Public key of the payer account (must be the community authority) * @param communityName The name of the community that the network should belong to * @param reporterPubkey Public key of the reporter * @param reporterType Type of the reporter * @param reporterName The name of the reporter to create * @returns Transaction to sign **/ async createReporterTransaction(reporterPubkey, reporterType, re