cashscript 0.13.0-next.2 → 0.13.0-next.4

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.
@@ -1,30 +1,38 @@
1
- import { Artifact, Script } from '@cashscript/utils';
1
+ import { Artifact } from '@cashscript/utils';
2
2
  import { ConstructorArgument } from './Argument.js';
3
- import { Unlocker, ContractOptions, Utxo, AddressType } from './interfaces.js';
3
+ import { Unlocker, ContractOptions, Utxo, ContractType } from './interfaces.js';
4
4
  import NetworkProvider from './network/NetworkProvider.js';
5
5
  import { ParamsToTuple, AbiToFunctionMap } from './types/type-inference.js';
6
- export declare class Contract<TArtifact extends Artifact = Artifact, TResolved extends {
6
+ type ResolvedConstraint = {
7
7
  constructorInputs: ConstructorArgument[];
8
8
  unlock: Record<string, any>;
9
- } = {
9
+ };
10
+ type DefaultResolved<TArtifact extends Artifact> = {
10
11
  constructorInputs: ParamsToTuple<TArtifact['constructorInputs']>;
11
12
  unlock: AbiToFunctionMap<TArtifact['abi'], Unlocker>;
12
- }> {
13
+ };
14
+ declare class ContractInternal<TArtifact extends Artifact, TResolved extends ResolvedConstraint, TContractType extends ContractType> {
13
15
  artifact: TArtifact;
14
16
  private options;
15
17
  name: string;
16
18
  address: string;
17
19
  tokenAddress: string;
20
+ lockingBytecode: string;
18
21
  bytecode: string;
19
22
  bytesize: number;
20
23
  opcount: number;
21
24
  unlock: TResolved['unlock'];
22
- redeemScript: Script;
23
25
  provider: NetworkProvider;
24
- addressType: AddressType;
26
+ contractType: TContractType;
25
27
  encodedConstructorArgs: Uint8Array[];
26
- constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options: ContractOptions);
28
+ constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options: ContractOptions<TContractType>);
27
29
  getBalance(): Promise<bigint>;
28
30
  getUtxos(): Promise<Utxo[]>;
29
31
  private createUnlocker;
30
32
  }
33
+ export type Contract<TArtifact extends Artifact = Artifact, TResolved extends ResolvedConstraint = DefaultResolved<TArtifact>, TContractType extends ContractType = ContractType> = [TContractType] extends ['p2s'] ? Omit<ContractInternal<TArtifact, TResolved, TContractType>, 'address' | 'tokenAddress'> : ContractInternal<TArtifact, TResolved, TContractType>;
34
+ interface ContractConstructor {
35
+ new <TArtifact extends Artifact = Artifact, TResolved extends ResolvedConstraint = DefaultResolved<TArtifact>, TContractType extends ContractType = 'p2sh32'>(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options: ContractOptions<TContractType>): Contract<TArtifact, TResolved, TContractType>;
36
+ }
37
+ export declare const Contract: ContractConstructor;
38
+ export {};
package/dist/Contract.js CHANGED
@@ -1,15 +1,18 @@
1
- import { binToHex } from '@bitauth/libauth';
2
- import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
1
+ import { binToHex, hexToBin } from '@bitauth/libauth';
2
+ import { asmToScript, calculateBytesize, countOpcodes, generateContractBytecodeScript, hash256, scriptToBytecode, } from '@cashscript/utils';
3
3
  import { encodeFunctionArgument, encodeConstructorArguments, } from './Argument.js';
4
- import { addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress, } from './utils.js';
4
+ import { addressToLockScript, createUnlockingBytecode, createSighashPreimage, scriptToAddress, } from './utils.js';
5
5
  import SignatureTemplate from './SignatureTemplate.js';
6
6
  import semver from 'semver';
7
- export class Contract {
7
+ class ContractInternal {
8
8
  constructor(artifact, constructorArgs, options) {
9
9
  this.artifact = artifact;
10
10
  this.options = options;
11
11
  this.provider = this.options.provider;
12
- this.addressType = this.options.addressType ?? 'p2sh32';
12
+ // Note: technically, it is possible to instantiate a Contract like this, which breaks the type safety,
13
+ // but it seems unreasonable for anyone to do this
14
+ // new Contract<any, any, 'p2s'>(artifact), [], { provider })
15
+ this.contractType = this.options.contractType ?? 'p2sh32';
13
16
  const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName', 'compiler'];
14
17
  if (!expectedProperties.every((property) => property in artifact)) {
15
18
  throw new Error('Invalid or incomplete artifact provided');
@@ -22,7 +25,6 @@ export class Contract {
22
25
  }
23
26
  // Encode arguments (this also performs type checking)
24
27
  this.encodedConstructorArgs = encodeConstructorArguments(artifact, constructorArgs);
25
- this.redeemScript = generateRedeemScript(asmToScript(this.artifact.bytecode), this.encodedConstructorArgs);
26
28
  // Populate the 'unlock' object with the contract's functions
27
29
  // (with a special case for single function, which has no "function selector")
28
30
  this.unlock = {};
@@ -37,18 +39,25 @@ export class Contract {
37
39
  this.unlock[f.name] = this.createUnlocker(f, i);
38
40
  });
39
41
  }
42
+ const contractBytecodeScript = generateContractBytecodeScript(asmToScript(this.artifact.bytecode), this.encodedConstructorArgs);
43
+ if (this.contractType !== 'p2s') {
44
+ this.address = scriptToAddress(contractBytecodeScript, this.provider.network, this.contractType, false);
45
+ this.tokenAddress = scriptToAddress(contractBytecodeScript, this.provider.network, this.contractType, true);
46
+ }
40
47
  this.name = artifact.contractName;
41
- this.address = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, false);
42
- this.tokenAddress = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, true);
43
- this.bytecode = binToHex(scriptToBytecode(this.redeemScript));
44
- this.bytesize = calculateBytesize(this.redeemScript);
45
- this.opcount = countOpcodes(this.redeemScript);
48
+ this.bytecode = binToHex(scriptToBytecode(contractBytecodeScript));
49
+ this.lockingBytecode = this.contractType === 'p2s' ? this.bytecode : binToHex(addressToLockScript(this.address));
50
+ this.bytesize = calculateBytesize(contractBytecodeScript);
51
+ this.opcount = countOpcodes(contractBytecodeScript);
46
52
  }
47
53
  async getBalance() {
48
54
  const utxos = await this.getUtxos();
49
55
  return utxos.reduce((acc, utxo) => acc + utxo.satoshis, 0n);
50
56
  }
51
57
  async getUtxos() {
58
+ if (this.contractType === 'p2s') {
59
+ return this.provider.getUtxosForLockingBytecode(this.bytecode);
60
+ }
52
61
  return this.provider.getUtxos(this.address);
53
62
  }
54
63
  createUnlocker(abiFunction, selector) {
@@ -56,7 +65,7 @@ export class Contract {
56
65
  if (abiFunction.inputs.length !== args.length) {
57
66
  throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map((input) => input.type)}) but got ${args.length}`);
58
67
  }
59
- const bytecode = scriptToBytecode(this.redeemScript);
68
+ const bytecode = hexToBin(this.bytecode);
60
69
  const encodedArgs = args
61
70
  .map((arg, i) => encodeFunctionArgument(arg, abiFunction.inputs[i].type));
62
71
  const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
@@ -68,7 +77,7 @@ export class Contract {
68
77
  const sighash = hash256(preimage);
69
78
  return arg.generateSignature(sighash);
70
79
  });
71
- const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector);
80
+ const unlockingBytecode = createUnlockingBytecode(this.contractType, hexToBin(this.bytecode), completeArgs, selector);
72
81
  return unlockingBytecode;
73
82
  };
74
83
  const generateLockingBytecode = () => addressToLockScript(this.address);
@@ -76,4 +85,5 @@ export class Contract {
76
85
  };
77
86
  }
78
87
  }
88
+ export const Contract = ContractInternal;
79
89
  //# sourceMappingURL=Contract.js.map
@@ -43,6 +43,7 @@ export type StandardUnlocker = ContractUnlocker | P2PKHUnlocker;
43
43
  export type PlaceholderP2PKHUnlocker = Unlocker & {
44
44
  placeholder: true;
45
45
  };
46
+ export type ContractFunctionUnlocker = (...args: FunctionArgument[]) => ContractUnlocker;
46
47
  export declare function isContractUnlocker(unlocker: Unlocker): unlocker is ContractUnlocker;
47
48
  export declare function isP2PKHUnlocker(unlocker: Unlocker): unlocker is P2PKHUnlocker;
48
49
  export declare function isStandardUnlocker(unlocker: Unlocker): unlocker is StandardUnlocker;
@@ -114,9 +115,10 @@ export interface TransactionDetails extends Transaction {
114
115
  txid: string;
115
116
  hex: string;
116
117
  }
117
- export interface ContractOptions {
118
+ export interface ContractOptions<TContractType extends ContractType = ContractType> {
118
119
  provider: NetworkProvider;
119
- addressType?: AddressType;
120
+ contractType?: TContractType;
120
121
  }
122
+ export type ContractType = 'p2sh20' | 'p2sh32' | 'p2s';
121
123
  export type AddressType = 'p2sh20' | 'p2sh32';
122
124
  export type VmResourceUsage = AuthenticationProgramStateResourceLimits['metrics'];
@@ -180,7 +180,7 @@ const generateTemplateScriptsP2SH = (contract, abiFunction, encodedFunctionArgs,
180
180
  };
181
181
  const generateTemplateLockScript = (contract, constructorArguments) => {
182
182
  return {
183
- lockingType: contract.addressType,
183
+ lockingType: contract.contractType === 'p2s' ? 'standard' : contract.contractType,
184
184
  name: contract.artifact.contractName,
185
185
  script: [
186
186
  `// "${contract.artifact.contractName}" contract constructor parameters`,
@@ -9,6 +9,7 @@ export default class BitcoinRpcNetworkProvider implements NetworkProvider {
9
9
  rpcPassword: string;
10
10
  });
11
11
  getUtxos(address: string): Promise<Utxo[]>;
12
+ getUtxosForLockingBytecode(_lockingBytecode: Uint8Array | string): Promise<Utxo[]>;
12
13
  getBlockHeight(): Promise<number>;
13
14
  getRawTransaction(txid: string): Promise<string>;
14
15
  sendRawTransaction(txHex: string): Promise<string>;
@@ -13,6 +13,9 @@ export default class BitcoinRpcNetworkProvider {
13
13
  }));
14
14
  return utxos;
15
15
  }
16
+ async getUtxosForLockingBytecode(_lockingBytecode) {
17
+ throw new Error('BitcoinRpcNetworkProvider does not support getUtxosForLockingBytecode');
18
+ }
16
19
  async getBlockHeight() {
17
20
  return this.rpcClient.request('getblockcount');
18
21
  }
@@ -20,6 +20,7 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
20
20
  private instantiateElectrumClient;
21
21
  private getServerForNetwork;
22
22
  getUtxos(address: string): Promise<Utxo[]>;
23
+ getUtxosForLockingBytecode(lockingBytecode: Uint8Array | string): Promise<Utxo[]>;
23
24
  getBlockHeight(): Promise<number>;
24
25
  getRawTransaction(txid: string): Promise<string>;
25
26
  sendRawTransaction(txHex: string): Promise<string>;
@@ -1,4 +1,4 @@
1
- import { binToHex } from '@bitauth/libauth';
1
+ import { binToHex, hexToBin, isHex } from '@bitauth/libauth';
2
2
  import { sha256 } from '@cashscript/utils';
3
3
  import { ElectrumClient, } from '@electrum-cash/network';
4
4
  import { Network } from '../interfaces.js';
@@ -32,9 +32,17 @@ export default class ElectrumNetworkProvider {
32
32
  }
33
33
  }
34
34
  async getUtxos(address) {
35
- const scripthash = addressToElectrumScriptHash(address);
35
+ const lockingBytecode = addressToLockScript(address);
36
+ return this.getUtxosForLockingBytecode(lockingBytecode);
37
+ }
38
+ async getUtxosForLockingBytecode(lockingBytecode) {
39
+ if (typeof lockingBytecode === 'string' && !isHex(lockingBytecode)) {
40
+ throw new Error(`Invalid locking bytecode: ${lockingBytecode} is not a valid hex string`);
41
+ }
42
+ const lockingBytecodeBin = typeof lockingBytecode === 'string' ? hexToBin(lockingBytecode) : lockingBytecode;
43
+ const scriptHash = lockingBytecodeToElectrumScriptHash(lockingBytecodeBin);
36
44
  const filteringOption = 'include_tokens';
37
- const result = await this.performRequest('blockchain.scripthash.listunspent', scripthash, filteringOption);
45
+ const result = await this.performRequest('blockchain.scripthash.listunspent', scriptHash, filteringOption);
38
46
  const utxos = result.map((utxo) => ({
39
47
  txid: utxo.tx_hash,
40
48
  vout: utxo.tx_pos,
@@ -105,23 +113,9 @@ export default class ElectrumNetworkProvider {
105
113
  return true;
106
114
  }
107
115
  }
108
- /**
109
- * Helper function to convert an address to an electrum-cash compatible scripthash.
110
- * This is necessary to support electrum versions lower than 1.4.3, which do not
111
- * support addresses, only script hashes.
112
- *
113
- * @param address Address to convert to an electrum scripthash
114
- *
115
- * @returns The corresponding script hash in an electrum-cash compatible format
116
- */
117
- function addressToElectrumScriptHash(address) {
118
- // Retrieve locking script
119
- const lockScript = addressToLockScript(address);
120
- // Hash locking script
121
- const scriptHash = sha256(lockScript);
122
- // Reverse scripthash
116
+ function lockingBytecodeToElectrumScriptHash(lockingBytecode) {
117
+ const scriptHash = sha256(lockingBytecode);
123
118
  scriptHash.reverse();
124
- // Return scripthash as a hex string
125
119
  return binToHex(scriptHash);
126
120
  }
127
121
  //# sourceMappingURL=ElectrumNetworkProvider.js.map
@@ -13,6 +13,7 @@ export default class FullStackNetworkProvider implements NetworkProvider {
13
13
  */
14
14
  constructor(network: Network, bchjs: BCHJS);
15
15
  getUtxos(address: string): Promise<Utxo[]>;
16
+ getUtxosForLockingBytecode(_lockingBytecode: Uint8Array | string): Promise<Utxo[]>;
16
17
  getBlockHeight(): Promise<number>;
17
18
  getRawTransaction(txid: string): Promise<string>;
18
19
  sendRawTransaction(txHex: string): Promise<string>;
@@ -20,6 +20,9 @@ export default class FullStackNetworkProvider {
20
20
  }));
21
21
  return utxos;
22
22
  }
23
+ async getUtxosForLockingBytecode(_lockingBytecode) {
24
+ throw new Error('FullStackNetworkProvider does not support getUtxosForLockingBytecode');
25
+ }
23
26
  async getBlockHeight() {
24
27
  return this.bchjs.Blockchain.getBlockCount();
25
28
  }
@@ -13,6 +13,7 @@ export default class MockNetworkProvider implements NetworkProvider {
13
13
  vmTarget: VmTarget;
14
14
  constructor(options?: Partial<MockNetworkProviderOptions>);
15
15
  getUtxos(address: string): Promise<Utxo[]>;
16
+ getUtxosForLockingBytecode(lockingBytecode: Uint8Array | string): Promise<Utxo[]>;
16
17
  setBlockHeight(newBlockHeight: number): void;
17
18
  getBlockHeight(): Promise<number>;
18
19
  getRawTransaction(txid: string): Promise<string>;
@@ -14,8 +14,12 @@ export default class MockNetworkProvider {
14
14
  this.vmTarget = this.options.vmTarget ?? DEFAULT_VM_TARGET;
15
15
  }
16
16
  async getUtxos(address) {
17
- const addressLockingBytecode = binToHex(addressToLockScript(address));
18
- return this.utxoSet.filter(([lockingBytecode]) => lockingBytecode === addressLockingBytecode).map(([, utxo]) => utxo);
17
+ const addressLockingBytecode = addressToLockScript(address);
18
+ return this.getUtxosForLockingBytecode(addressLockingBytecode);
19
+ }
20
+ async getUtxosForLockingBytecode(lockingBytecode) {
21
+ const lockingBytecodeHex = typeof lockingBytecode === 'string' ? lockingBytecode : binToHex(lockingBytecode);
22
+ return this.utxoSet.filter(([key]) => key === lockingBytecodeHex).map(([, utxo]) => utxo);
19
23
  }
20
24
  setBlockHeight(newBlockHeight) {
21
25
  this.blockHeight = newBlockHeight;
@@ -10,6 +10,12 @@ export default interface NetworkProvider {
10
10
  * @returns List of UTXOs spendable by the provided address.
11
11
  */
12
12
  getUtxos(address: string): Promise<Utxo[]>;
13
+ /**
14
+ * Retrieve all UTXOs (confirmed and unconfirmed) for a given locking bytecode.
15
+ * @param lockingBytecode The locking bytecode for which we wish to retrieve UTXOs.
16
+ * @returns List of UTXOs spendable by the provided locking bytecode.
17
+ */
18
+ getUtxosForLockingBytecode(lockingBytecode: Uint8Array | string): Promise<Utxo[]>;
13
19
  /**
14
20
  * @returns The current block height.
15
21
  */
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Transaction } from '@bitauth/libauth';
2
2
  import { Script } from '@cashscript/utils';
3
- import { Utxo, Output, LibauthOutput, TokenDetails, AddressType, UnlockableUtxo, LibauthTokenDetails } from './interfaces.js';
3
+ import { Utxo, Output, LibauthOutput, TokenDetails, AddressType, UnlockableUtxo, LibauthTokenDetails, ContractType } from './interfaces.js';
4
4
  export declare function validateInput(utxo: Utxo): void;
5
5
  export declare function validateOutput(output: Output): void;
6
6
  export declare function calculateDust(output: Output): number;
@@ -12,12 +12,12 @@ export declare function libauthTokenDetailsToCashScriptTokenDetails(token: Libau
12
12
  export declare function generateLibauthSourceOutputs(inputs: UnlockableUtxo[]): LibauthOutput[];
13
13
  export declare function getInputSize(inputScript: Uint8Array): number;
14
14
  export declare function getTxSizeWithoutInputs(outputs: Output[]): number;
15
- export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number): Uint8Array;
15
+ export declare function createUnlockingBytecode(contractType: ContractType, contractBytecode: Uint8Array, encodedArgs: Uint8Array[], selector?: number): Uint8Array;
16
16
  export declare function createOpReturnOutput(opReturnData: string[]): Output;
17
17
  export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
18
18
  export declare function toRegExp(reasons: string[]): RegExp;
19
19
  export declare function scriptToAddress(script: Script, network: string, addressType: AddressType, tokenSupport: boolean): string;
20
- export declare function scriptToLockingBytecode(script: Script, addressType: AddressType): Uint8Array;
20
+ export declare function scriptToP2SHLockingBytecode(script: Script, addressType: AddressType): Uint8Array;
21
21
  export declare function publicKeyToP2PKHLockingBytecode(publicKey: Uint8Array): Uint8Array;
22
22
  export declare function utxoComparator(a: Utxo, b: Utxo): number;
23
23
  export declare function utxoTokenComparator(a: Utxo, b: Utxo): number;
package/dist/utils.js CHANGED
@@ -123,13 +123,15 @@ export function getTxSizeWithoutInputs(outputs) {
123
123
  return size;
124
124
  }
125
125
  // ////////// BUILD OBJECTS ///////////////////////////////////////////////////
126
- export function createInputScript(redeemScript, encodedArgs, selector) {
127
- // Create unlock script / redeemScriptSig (add potential selector)
126
+ export function createUnlockingBytecode(contractType, contractBytecode, encodedArgs, selector) {
128
127
  const unlockScript = [...encodedArgs].reverse();
129
128
  if (selector !== undefined)
130
129
  unlockScript.push(encodeInt(BigInt(selector)));
130
+ // P2S inputs do not need to provide the redeem script
131
+ if (contractType === 'p2s')
132
+ return scriptToBytecode(unlockScript);
131
133
  // Create input script and compile it to bytecode
132
- const inputScript = [...unlockScript, scriptToBytecode(redeemScript)];
134
+ const inputScript = [...unlockScript, contractBytecode];
133
135
  return scriptToBytecode(inputScript);
134
136
  }
135
137
  export function createOpReturnOutput(opReturnData) {
@@ -154,14 +156,14 @@ export function toRegExp(reasons) {
154
156
  return new RegExp(reasons.join('|').replace(/\(/g, '\\(').replace(/\)/g, '\\)'));
155
157
  }
156
158
  export function scriptToAddress(script, network, addressType, tokenSupport) {
157
- const bytecode = scriptToLockingBytecode(script, addressType);
159
+ const bytecode = scriptToP2SHLockingBytecode(script, addressType);
158
160
  const prefix = getNetworkPrefix(network);
159
161
  const result = lockingBytecodeToCashAddress({ bytecode, prefix, tokenSupport });
160
162
  if (typeof result === 'string')
161
163
  throw new Error(result);
162
164
  return result.address;
163
165
  }
164
- export function scriptToLockingBytecode(script, addressType) {
166
+ export function scriptToP2SHLockingBytecode(script, addressType) {
165
167
  const scriptBytecode = scriptToBytecode(script);
166
168
  const scriptHash = (addressType === 'p2sh20') ? hash160(scriptBytecode) : hash256(scriptBytecode);
167
169
  const addressContents = { payload: scriptHash, type: LockingBytecodeType[addressType] };
@@ -1,6 +1,5 @@
1
1
  import { isContractUnlocker } from './interfaces.js';
2
- import { scriptToBytecode } from '@cashscript/utils';
3
- import { cashAddressToLockingBytecode } from '@bitauth/libauth';
2
+ import { cashAddressToLockingBytecode, hexToBin } from '@bitauth/libauth';
4
3
  export function getWcContractInfo(input) {
5
4
  // If the input does not have a contract unlocker, return an empty object
6
5
  if (!(isContractUnlocker(input.unlocker)))
@@ -14,7 +13,7 @@ export function getWcContractInfo(input) {
14
13
  const wcContractObj = {
15
14
  contract: {
16
15
  abiFunction: abiFunction,
17
- redeemScript: scriptToBytecode(contract.redeemScript),
16
+ redeemScript: hexToBin(contract.bytecode),
18
17
  artifact: contract.artifact,
19
18
  },
20
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.13.0-next.2",
3
+ "version": "0.13.0-next.4",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@bitauth/libauth": "^3.1.0-next.8",
45
- "@cashscript/utils": "^0.13.0-next.2",
45
+ "@cashscript/utils": "^0.13.0-next.4",
46
46
  "@electrum-cash/network": "^4.1.3",
47
47
  "@mr-zwets/bchn-api-wrapper": "^1.0.1",
48
48
  "fflate": "^0.8.2",
@@ -56,5 +56,5 @@
56
56
  "typescript": "^5.9.2",
57
57
  "vitest": "^4.0.15"
58
58
  },
59
- "gitHead": "817391f108ce46c826dbab6f11ee4f1bb269c27f"
59
+ "gitHead": "a9b4062bda82a2d80adf1bf21b846a62d7592e5b"
60
60
  }