@alephium/web3 1.11.5 → 1.12.0-beta.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.
Files changed (37) hide show
  1. package/dist/alephium-web3.min.js +1 -1
  2. package/dist/alephium-web3.min.js.map +1 -1
  3. package/dist/src/address/address.d.ts +11 -1
  4. package/dist/src/address/address.js +117 -11
  5. package/dist/src/api/api-alephium.d.ts +101 -4
  6. package/dist/src/api/api-alephium.js +51 -1
  7. package/dist/src/api/node-provider.d.ts +2 -0
  8. package/dist/src/api/node-provider.js +1 -0
  9. package/dist/src/api/types.d.ts +1 -1
  10. package/dist/src/api/types.js +10 -5
  11. package/dist/src/codec/lockup-script-codec.d.ts +11 -2
  12. package/dist/src/codec/lockup-script-codec.js +8 -1
  13. package/dist/src/codec/unlock-script-codec.d.ts +11 -3
  14. package/dist/src/codec/unlock-script-codec.js +14 -4
  15. package/dist/src/contract/contract.d.ts +3 -3
  16. package/dist/src/contract/contract.js +12 -5
  17. package/dist/src/contract/ralph.js +2 -1
  18. package/dist/src/signer/signer.js +1 -1
  19. package/dist/src/signer/tx-builder.d.ts +7 -1
  20. package/dist/src/signer/tx-builder.js +67 -0
  21. package/dist/src/signer/types.d.ts +29 -2
  22. package/dist/src/signer/types.js +3 -0
  23. package/dist/src/utils/sign.js +2 -2
  24. package/dist/src/utils/webcrypto.d.ts +3 -1
  25. package/package.json +3 -3
  26. package/src/address/address.ts +120 -12
  27. package/src/api/api-alephium.ts +474 -144
  28. package/src/api/node-provider.ts +3 -0
  29. package/src/api/types.ts +10 -5
  30. package/src/codec/lockup-script-codec.ts +19 -2
  31. package/src/codec/unlock-script-codec.ts +23 -8
  32. package/src/contract/contract.ts +33 -10
  33. package/src/contract/ralph.ts +5 -2
  34. package/src/signer/signer.ts +1 -1
  35. package/src/signer/tx-builder.ts +88 -1
  36. package/src/signer/types.ts +36 -2
  37. package/src/utils/sign.ts +2 -2
@@ -84,7 +84,7 @@ export declare class Contract extends Artifact {
84
84
  static DebugEventIndex: number;
85
85
  static fromApiEvent(event: node.ContractEventByTxId, codeHash: string | undefined, txId: string, getContractByCodeHash: (codeHash: string) => Contract): ContractEvent;
86
86
  fromApiTestContractResult(methodName: string, result: node.TestContractResult, txId: string, getContractByCodeHash: (codeHash: string) => Contract): TestContractResult<unknown>;
87
- txParamsForDeployment<P extends Fields>(signer: SignerProvider, params: DeployContractParams<P>): Promise<SignDeployContractTxParams>;
87
+ txParamsForDeployment<P extends Fields>(signer: SignerProvider, params: DeployContractParams<P>, group?: number): Promise<SignDeployContractTxParams>;
88
88
  buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean, exposePrivateFunctions?: boolean): string;
89
89
  static fromApiEvents(events: node.ContractEventByTxId[], addressToCodeHash: Map<string, string>, txId: string, getContractByCodeHash: (codeHash: string) => Contract): ContractEvent[];
90
90
  toApiCallContract<T extends Arguments>(params: CallContractParams<T>, groupIndex: number, contractAddress: string, methodIndex: number): node.CallContract;
@@ -132,7 +132,7 @@ export type TestContractParamsWithoutMaps<F extends Fields = Fields, A extends A
132
132
  export interface TestContractParams<F extends Fields = Fields, A extends Arguments = Arguments, M extends Record<string, Map<Val, Val>> = Record<string, Map<Val, Val>>> {
133
133
  group?: number;
134
134
  address?: string;
135
- callerAddress?: string;
135
+ callerContractAddress?: string;
136
136
  blockHash?: string;
137
137
  blockTimeStamp?: number;
138
138
  txId?: string;
@@ -195,7 +195,7 @@ export declare abstract class ContractFactory<I extends ContractInstance, F exte
195
195
  readonly contract: Contract;
196
196
  constructor(contract: Contract);
197
197
  abstract at(address: string): I;
198
- deploy(signer: SignerProvider, deployParams: DeployContractParams<F>): Promise<DeployContractResult<I>>;
198
+ deploy(signer: SignerProvider, deployParams: DeployContractParams<F>, group?: number): Promise<DeployContractResult<I>>;
199
199
  deployTemplate(signer: SignerProvider): Promise<DeployContractResult<I>>;
200
200
  protected stateForTest_(initFields: F, asset?: Asset, address?: string, maps?: Record<string, Map<Val, Val>>): ContractState<F> | ContractStateWithMaps<F>;
201
201
  }
@@ -310,7 +310,7 @@ class Contract extends Artifact {
310
310
  blockTimeStamp: params.blockTimeStamp,
311
311
  txId: params.txId,
312
312
  address: params.address,
313
- callerAddress: params.callerAddress,
313
+ callerContractAddress: params.callerContractAddress,
314
314
  bytecode: this.isInlineFunc(methodIndex) ? this.getByteCodeForTesting() : this.bytecodeDebug,
315
315
  initialImmFields: immFields,
316
316
  initialMutFields: mutFields,
@@ -382,13 +382,20 @@ class Contract extends Artifact {
382
382
  debugMessages: result.debugMessages
383
383
  };
384
384
  }
385
- async txParamsForDeployment(signer, params) {
385
+ async txParamsForDeployment(signer, params, group) {
386
386
  const isDevnet = await this.isDevnet(signer);
387
387
  const initialFields = params.initialFields ?? {};
388
388
  const bytecode = this.buildByteCodeToDeploy(addStdIdToFields(this, initialFields), isDevnet, params.exposePrivateFunctions ?? false);
389
389
  const selectedAccount = await signer.getSelectedAccount();
390
+ let signerAddress = selectedAccount.address;
391
+ if ((0, address_1.isGrouplessAddressWithoutGroupIndex)(selectedAccount.address)) {
392
+ if (group === undefined) {
393
+ throw new Error('Groupless address requires explicit group number for contract deployment');
394
+ }
395
+ signerAddress = `${selectedAccount.address}:${group}`;
396
+ }
390
397
  const signerParams = {
391
- signerAddress: selectedAccount.address,
398
+ signerAddress,
392
399
  signerKeyType: selectedAccount.keyType,
393
400
  bytecode: bytecode,
394
401
  initialAttoAlphAmount: params?.initialAttoAlphAmount,
@@ -678,11 +685,11 @@ class ContractFactory {
678
685
  constructor(contract) {
679
686
  this.contract = contract;
680
687
  }
681
- async deploy(signer, deployParams) {
688
+ async deploy(signer, deployParams, group) {
682
689
  const signerParams = await this.contract.txParamsForDeployment(signer, {
683
690
  ...deployParams,
684
691
  initialFields: addStdIdToFields(this.contract, deployParams.initialFields)
685
- });
692
+ }, group);
686
693
  const result = await signer.signAndSubmitDeployContractTx(signerParams);
687
694
  return {
688
695
  ...result,
@@ -23,6 +23,7 @@ const utils_1 = require("../utils");
23
23
  const codec_1 = require("../codec");
24
24
  const codec_2 = require("../codec/codec");
25
25
  const error_1 = require("../error");
26
+ const address_1 = require("../address");
26
27
  function encodeByteVec(hex) {
27
28
  if (!(0, utils_1.isHexString)(hex)) {
28
29
  throw Error(`Given value ${hex} is not a valid hex string`);
@@ -32,7 +33,7 @@ function encodeByteVec(hex) {
32
33
  }
33
34
  exports.encodeByteVec = encodeByteVec;
34
35
  function encodeAddress(address) {
35
- return utils_1.bs58.decode(address);
36
+ return (0, address_1.addressToBytes)(address);
36
37
  }
37
38
  exports.encodeAddress = encodeAddress;
38
39
  var VmValType;
@@ -240,6 +240,6 @@ function toApiDestinations(data) {
240
240
  }
241
241
  exports.toApiDestinations = toApiDestinations;
242
242
  function fromApiDestination(data) {
243
- return { ...data, attoAlphAmount: (0, api_1.fromApiNumber256)(data.attoAlphAmount), tokens: (0, api_1.fromApiTokens)(data.tokens) };
243
+ return { ...data, attoAlphAmount: (0, api_1.fromApiNumber256)(data.attoAlphAmount ?? '0'), tokens: (0, api_1.fromApiTokens)(data.tokens) };
244
244
  }
245
245
  exports.fromApiDestination = fromApiDestination;
@@ -1,5 +1,5 @@
1
1
  import { NodeProvider } from '../api';
2
- import { SignChainedTxParams, SignChainedTxResult, SignDeployContractTxParams, SignDeployContractTxResult, SignExecuteScriptTxParams, SignExecuteScriptTxResult, SignTransferTxParams, SignTransferTxResult, SignUnsignedTxParams, SignUnsignedTxResult } from './types';
2
+ import { SignChainedTxParams, SignChainedTxResult, SignDeployContractTxParams, SignDeployContractTxResult, SignExecuteScriptTxParams, SignExecuteScriptTxResult, SignTransferTxParams, SignTransferTxResult, SignUnsignedTxParams, SignUnsignedTxResult, SignGrouplessTransferTxParams, SignGrouplessDeployContractTxParams, SignGrouplessExecuteScriptTxParams } from './types';
3
3
  export declare abstract class TransactionBuilder {
4
4
  abstract get nodeProvider(): NodeProvider;
5
5
  static from(nodeProvider: NodeProvider): TransactionBuilder;
@@ -9,8 +9,14 @@ export declare abstract class TransactionBuilder {
9
9
  buildDeployContractTx(params: SignDeployContractTxParams, publicKey: string): Promise<Omit<SignDeployContractTxResult, 'signature'>>;
10
10
  buildExecuteScriptTx(params: SignExecuteScriptTxParams, publicKey: string): Promise<Omit<SignExecuteScriptTxResult, 'signature'>>;
11
11
  buildChainedTx(params: SignChainedTxParams[], publicKeys: string[]): Promise<Omit<SignChainedTxResult, 'signature'>[]>;
12
+ buildGrouplessTransferTx(params: SignGrouplessTransferTxParams): Promise<Omit<SignChainedTxResult, 'signature'>[]>;
13
+ buildGrouplessDeployContractTx(params: SignGrouplessDeployContractTxParams): Promise<Omit<SignChainedTxResult, 'signature'>[]>;
14
+ buildGrouplessExecuteScriptTx(params: SignGrouplessExecuteScriptTxParams): Promise<Omit<SignChainedTxResult, 'signature'>[]>;
12
15
  static buildUnsignedTx(params: SignUnsignedTxParams): Omit<SignUnsignedTxResult, 'signature'>;
13
16
  private buildTransferTxParams;
17
+ private buildGrouplessTransferTxParams;
18
+ private buildGrouplessDeployContractTxParams;
19
+ private buildGrouplessExecuteScriptTxParams;
14
20
  private buildDeployContractTxParams;
15
21
  private buildExecuteScriptTxParams;
16
22
  private convertTransferTxResult;
@@ -109,6 +109,42 @@ class TransactionBuilder {
109
109
  });
110
110
  return results;
111
111
  }
112
+ async buildGrouplessTransferTx(params) {
113
+ const data = this.buildGrouplessTransferTxParams(params);
114
+ const response = await this.nodeProvider.groupless.postGrouplessTransfer(data);
115
+ return response.map((result) => {
116
+ return {
117
+ ...this.convertTransferTxResult(result),
118
+ type: 'Transfer'
119
+ };
120
+ });
121
+ }
122
+ async buildGrouplessDeployContractTx(params) {
123
+ const data = this.buildGrouplessDeployContractTxParams(params);
124
+ const response = await this.nodeProvider.groupless.postGrouplessDeployContract(data);
125
+ const transferTxs = response.transferTxs.map((result) => ({
126
+ ...this.convertTransferTxResult(result),
127
+ type: 'Transfer'
128
+ }));
129
+ const deployContractTx = {
130
+ ...this.convertDeployContractTxResult(response.deployContractTx),
131
+ type: 'DeployContract'
132
+ };
133
+ return [...transferTxs, deployContractTx];
134
+ }
135
+ async buildGrouplessExecuteScriptTx(params) {
136
+ const data = this.buildGrouplessExecuteScriptTxParams(params);
137
+ const response = await this.nodeProvider.groupless.postGrouplessExecuteScript(data);
138
+ const transferTxs = response.transferTxs.map((result) => ({
139
+ ...this.convertTransferTxResult(result),
140
+ type: 'Transfer'
141
+ }));
142
+ const executeScriptTx = {
143
+ ...this.convertExecuteScriptTxResult(response.executeScriptTx),
144
+ type: 'ExecuteScript'
145
+ };
146
+ return [...transferTxs, executeScriptTx];
147
+ }
112
148
  static buildUnsignedTx(params) {
113
149
  const unsignedTxBin = (0, utils_1.hexToBinUnsafe)(params.unsignedTx);
114
150
  const decoded = codec_1.unsignedTxCodec.decode(unsignedTxBin);
@@ -134,6 +170,37 @@ class TransactionBuilder {
134
170
  ...rest
135
171
  };
136
172
  }
173
+ buildGrouplessTransferTxParams(params) {
174
+ return {
175
+ fromAddress: params.fromAddress,
176
+ destinations: (0, signer_1.toApiDestinations)(params.destinations),
177
+ gasPrice: (0, api_1.toApiNumber256Optional)(params.gasPrice),
178
+ targetBlockHash: params.targetBlockHash
179
+ };
180
+ }
181
+ buildGrouplessDeployContractTxParams(params) {
182
+ return {
183
+ fromAddress: params.fromAddress,
184
+ bytecode: params.bytecode,
185
+ initialAttoAlphAmount: (0, api_1.toApiNumber256Optional)(params.initialAttoAlphAmount),
186
+ initialTokenAmounts: (0, api_1.toApiTokens)(params.initialTokenAmounts),
187
+ issueTokenAmount: (0, api_1.toApiNumber256Optional)(params.issueTokenAmount),
188
+ issueTokenTo: params.issueTokenTo,
189
+ gasPrice: (0, api_1.toApiNumber256Optional)(params.gasPrice),
190
+ targetBlockHash: params.targetBlockHash
191
+ };
192
+ }
193
+ buildGrouplessExecuteScriptTxParams(params) {
194
+ return {
195
+ fromAddress: params.fromAddress,
196
+ bytecode: params.bytecode,
197
+ attoAlphAmount: (0, api_1.toApiNumber256Optional)(params.attoAlphAmount),
198
+ tokens: (0, api_1.toApiTokens)(params.tokens),
199
+ gasPrice: (0, api_1.toApiNumber256Optional)(params.gasPrice),
200
+ targetBlockHash: params.targetBlockHash,
201
+ gasEstimationMultiplier: params.gasEstimationMultiplier
202
+ };
203
+ }
137
204
  buildDeployContractTxParams(params, publicKey) {
138
205
  TransactionBuilder.validatePublicKey(params, publicKey, params.signerKeyType);
139
206
  const { initialAttoAlphAmount, initialTokenAmounts, issueTokenAmount, gasPrice, ...rest } = params;
@@ -1,5 +1,6 @@
1
1
  import { Number256, Token } from '../api';
2
2
  import { node } from '../api';
3
+ import { SimulationResult } from '../api/api-alephium';
3
4
  import { NetworkId } from '../utils';
4
5
  export type Address = string;
5
6
  export type OutputRef = node.OutputRef;
@@ -10,7 +11,7 @@ export interface Destination {
10
11
  lockTime?: number;
11
12
  message?: string;
12
13
  }
13
- export type KeyType = 'default' | 'bip340-schnorr';
14
+ export type KeyType = 'default' | 'bip340-schnorr' | 'groupless';
14
15
  export interface Account {
15
16
  keyType: KeyType;
16
17
  address: string;
@@ -76,7 +77,7 @@ export interface SignExecuteScriptTxResult {
76
77
  signature: string;
77
78
  gasAmount: number;
78
79
  gasPrice: Number256;
79
- simulatedOutputs: node.Output[];
80
+ simulationResult: SimulationResult;
80
81
  }
81
82
  export interface SignUnsignedTxParams {
82
83
  signerAddress: string;
@@ -112,6 +113,32 @@ export type SignExecuteScriptChainedTxResult = SignExecuteScriptTxResult & {
112
113
  type: 'ExecuteScript';
113
114
  };
114
115
  export type SignChainedTxResult = SignTransferChainedTxResult | SignDeployContractChainedTxResult | SignExecuteScriptChainedTxResult;
116
+ export interface SignGrouplessTransferTxParams {
117
+ fromAddress: string;
118
+ destinations: Destination[];
119
+ gasPrice?: Number256;
120
+ targetBlockHash?: string;
121
+ }
122
+ export interface SignGrouplessDeployContractTxParams {
123
+ fromAddress: string;
124
+ bytecode: string;
125
+ initialAttoAlphAmount?: Number256;
126
+ initialTokenAmounts?: Token[];
127
+ issueTokenAmount?: Number256;
128
+ issueTokenTo?: string;
129
+ gasPrice?: Number256;
130
+ targetBlockHash?: string;
131
+ }
132
+ export interface SignGrouplessExecuteScriptTxParams {
133
+ fromAddress: string;
134
+ bytecode: string;
135
+ attoAlphAmount?: Number256;
136
+ tokens?: Token[];
137
+ gasPrice?: Number256;
138
+ targetBlockHash?: string;
139
+ gasEstimationMultiplier?: number;
140
+ }
141
+ export type SignGrouplessTxParams = SignGrouplessTransferTxParams | SignGrouplessDeployContractTxParams | SignGrouplessExecuteScriptTxParams;
115
142
  export type MessageHasher = 'alephium' | 'sha256' | 'blake2b' | 'identity';
116
143
  export interface SignMessageParams {
117
144
  signerAddress: string;
@@ -28,3 +28,6 @@ utils_1.assertType;
28
28
  (0, utils_1.assertType)();
29
29
  utils_1.assertType;
30
30
  (0, utils_1.assertType)();
31
+ (0, utils_1.assertType)();
32
+ (0, utils_1.assertType)();
33
+ (0, utils_1.assertType)();
@@ -60,7 +60,7 @@ necc.utils.hmacSha256Sync = (key, ...messages) => {
60
60
  // hash has to be 32 bytes
61
61
  function sign(hash, privateKey, _keyType) {
62
62
  const keyType = _keyType ?? 'default';
63
- if (keyType === 'default') {
63
+ if (keyType === 'default' || keyType === 'groupless') {
64
64
  const key = ec.keyFromPrivate(privateKey);
65
65
  const signature = key.sign(hash);
66
66
  return (0, utils_1.encodeSignature)(signature);
@@ -74,7 +74,7 @@ exports.sign = sign;
74
74
  function verifySignature(hash, publicKey, signature, _keyType) {
75
75
  const keyType = _keyType ?? 'default';
76
76
  try {
77
- if (keyType === 'default') {
77
+ if (keyType === 'default' || keyType === 'groupless') {
78
78
  const key = ec.keyFromPublic(publicKey, 'hex');
79
79
  return key.verify(hash, (0, utils_1.signatureDecode)(ec, signature));
80
80
  }
@@ -1,4 +1,6 @@
1
+ /// <reference types="node" />
2
+ import { webcrypto } from 'crypto';
1
3
  export declare class WebCrypto {
2
- subtle: SubtleCrypto;
4
+ subtle: webcrypto.SubtleCrypto;
3
5
  getRandomValues<T extends ArrayBufferView | null>(array: T): T;
4
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alephium/web3",
3
- "version": "1.11.5",
3
+ "version": "1.12.0-beta.0",
4
4
  "description": "A JS/TS library to interact with the Alephium platform",
5
5
  "license": "GPL",
6
6
  "main": "dist/src/index.js",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "author": "Alephium dev <dev@alephium.org>",
35
35
  "config": {
36
- "alephium_version": "3.10.0",
36
+ "alephium_version": "3.12.2",
37
37
  "explorer_backend_version": "2.3.2"
38
38
  },
39
39
  "type": "commonjs",
@@ -45,7 +45,7 @@
45
45
  "bn.js": "5.2.1",
46
46
  "cross-fetch": "^3.1.5",
47
47
  "crypto-browserify": "^3.12.0",
48
- "elliptic": "6.6.0",
48
+ "elliptic": "6.6.1",
49
49
  "eventemitter3": "^4.0.7",
50
50
  "path-browserify": "^1.0.1",
51
51
  "stream-browserify": "^3.0.0"
@@ -24,10 +24,11 @@ import bs58, { base58ToBytes } from '../utils/bs58'
24
24
  import { binToHex, concatBytes, hexToBinUnsafe, isHexString, xorByte } from '../utils'
25
25
  import { KeyType } from '../signer'
26
26
  import { P2MPKH, lockupScriptCodec } from '../codec/lockup-script-codec'
27
- import { i32Codec } from '../codec'
27
+ import { i32Codec, intAs4BytesCodec } from '../codec'
28
28
  import { LockupScript } from '../codec/lockup-script-codec'
29
29
  import djb2 from '../utils/djb2'
30
30
  import { TraceableError } from '../error'
31
+ import { byteCodec } from '../codec/codec'
31
32
 
32
33
  const ec = new EC('secp256k1')
33
34
  const PublicKeyHashSize = 32
@@ -36,7 +37,8 @@ export enum AddressType {
36
37
  P2PKH = 0x00,
37
38
  P2MPKH = 0x01,
38
39
  P2SH = 0x02,
39
- P2C = 0x03
40
+ P2C = 0x03,
41
+ P2PK = 0x04
40
42
  }
41
43
 
42
44
  export function validateAddress(address: string) {
@@ -53,7 +55,7 @@ export function isValidAddress(address: string): boolean {
53
55
  }
54
56
 
55
57
  function decodeAndValidateAddress(address: string): Uint8Array {
56
- const decoded = base58ToBytes(address)
58
+ const decoded = addressToBytes(address)
57
59
  if (decoded.length === 0) throw new Error('Address is empty')
58
60
  const addressType = decoded[0]
59
61
  if (addressType === AddressType.P2MPKH) {
@@ -75,14 +77,77 @@ function decodeAndValidateAddress(address: string): Uint8Array {
75
77
  } else if (addressType === AddressType.P2PKH || addressType === AddressType.P2SH || addressType === AddressType.P2C) {
76
78
  // [type, ...hash]
77
79
  if (decoded.length === 33) return decoded
80
+ } else if (isGrouplessAddressWithGroup(decoded)) {
81
+ // [type, keyType, ...publicKey, ...checkSum, ...groupByte]
82
+ const publicKeyToIndex = decoded.length - 1 - 4
83
+ const publicKeyLikeBytes = decoded.slice(1, publicKeyToIndex)
84
+ const checksum = binToHex(decoded.slice(publicKeyToIndex, publicKeyToIndex + 4))
85
+ const expectedChecksum = binToHex(intAs4BytesCodec.encode(djb2(publicKeyLikeBytes)))
86
+ if (checksum !== expectedChecksum) {
87
+ throw new Error(`Invalid checksum for P2PK address: ${address}`)
88
+ }
89
+ const group = byteCodec.decode(decoded.slice(decoded.length - 1, decoded.length))
90
+ validateGroupIndex(group)
91
+
92
+ return decoded
78
93
  }
79
94
 
80
95
  throw new Error(`Invalid address: ${address}`)
81
96
  }
82
97
 
98
+ function isGrouplessAddressWithoutGroup(decoded: Uint8Array): boolean {
99
+ return decoded[0] === AddressType.P2PK && (decoded.length === 38 || decoded.length === 39)
100
+ }
101
+
102
+ function isGrouplessAddressWithGroup(decoded: Uint8Array): boolean {
103
+ return decoded[0] === AddressType.P2PK && (decoded.length === 39 || decoded.length === 40)
104
+ }
105
+
106
+ export function addressToBytes(address: string): Uint8Array {
107
+ if (hasExplicitGroupIndex(address)) {
108
+ const groupIndex = parseGroupIndex(address[address.length - 1])
109
+ const decoded = base58ToBytes(address.slice(0, address.length - 2))
110
+ if (isGrouplessAddressWithoutGroup(decoded)) {
111
+ const groupByte = byteCodec.encode(groupIndex)
112
+ return new Uint8Array([...decoded, ...groupByte])
113
+ }
114
+ throw new Error(`Invalid groupless address: ${address}`)
115
+ } else {
116
+ const decoded = base58ToBytes(address)
117
+ if (isGrouplessAddressWithoutGroup(decoded)) {
118
+ const group = defaultGroupOfGrouplessAddress(decoded.slice(2, decoded.length - 4))
119
+ const groupByte = byteCodec.encode(group)
120
+ return new Uint8Array([...decoded, ...groupByte])
121
+ }
122
+ return decoded
123
+ }
124
+ }
125
+
83
126
  export function isAssetAddress(address: string) {
84
127
  const addressType = decodeAndValidateAddress(address)[0]
85
- return addressType === AddressType.P2PKH || addressType === AddressType.P2MPKH || addressType === AddressType.P2SH
128
+ return (
129
+ addressType === AddressType.P2PKH ||
130
+ addressType === AddressType.P2MPKH ||
131
+ addressType === AddressType.P2SH ||
132
+ addressType === AddressType.P2PK
133
+ )
134
+ }
135
+
136
+ export function isGrouplessAddress(address: string) {
137
+ const addressType = decodeAndValidateAddress(address)[0]
138
+ return addressType === AddressType.P2PK
139
+ }
140
+
141
+ export function isGrouplessAddressWithoutGroupIndex(address: string) {
142
+ return !hasExplicitGroupIndex(address) && isGrouplessAddress(address)
143
+ }
144
+
145
+ export function isGrouplessAddressWithGroupIndex(address: string) {
146
+ return hasExplicitGroupIndex(address) && isGrouplessAddress(address)
147
+ }
148
+
149
+ export function defaultGroupOfGrouplessAddress(pubKey: Uint8Array): number {
150
+ return pubKey[pubKey.length - 1] & 0xff % TOTAL_NUMBER_OF_GROUPS
86
151
  }
87
152
 
88
153
  export function isContractAddress(address: string) {
@@ -101,6 +166,8 @@ export function groupOfAddress(address: string): number {
101
166
  return groupOfP2mpkhAddress(addressBody)
102
167
  } else if (addressType == AddressType.P2SH) {
103
168
  return groupOfP2shAddress(addressBody)
169
+ } else if (addressType == AddressType.P2PK) {
170
+ return groupOfP2pkAddress(addressBody)
104
171
  } else {
105
172
  // Contract Address
106
173
  const id = contractIdFromAddress(address)
@@ -110,17 +177,21 @@ export function groupOfAddress(address: string): number {
110
177
 
111
178
  // Pay to public key hash address
112
179
  function groupOfP2pkhAddress(address: Uint8Array): number {
113
- return groupFromBytesForAssetAddress(address)
180
+ return groupFromBytes(address)
114
181
  }
115
182
 
116
183
  // Pay to multiple public key hash address
117
184
  function groupOfP2mpkhAddress(address: Uint8Array): number {
118
- return groupFromBytesForAssetAddress(address.slice(1, 33))
185
+ return groupFromBytes(address.slice(1, 33))
186
+ }
187
+
188
+ function groupOfP2pkAddress(address: Uint8Array): number {
189
+ return byteCodec.decode(address.slice(38, 39)) % TOTAL_NUMBER_OF_GROUPS
119
190
  }
120
191
 
121
192
  // Pay to script hash address
122
193
  function groupOfP2shAddress(address: Uint8Array): number {
123
- return groupFromBytesForAssetAddress(address)
194
+ return groupFromBytes(address)
124
195
  }
125
196
 
126
197
  export function contractIdFromAddress(address: string): Uint8Array {
@@ -151,7 +222,7 @@ export function groupOfPrivateKey(privateKey: string, keyType?: KeyType): number
151
222
  export function publicKeyFromPrivateKey(privateKey: string, _keyType?: KeyType): string {
152
223
  const keyType = _keyType ?? 'default'
153
224
 
154
- if (keyType === 'default') {
225
+ if (keyType === 'default' || keyType === 'groupless') {
155
226
  const key = ec.keyFromPrivate(privateKey)
156
227
  return key.getPublic(true, 'hex')
157
228
  } else {
@@ -166,6 +237,11 @@ export function addressFromPublicKey(publicKey: string, _keyType?: KeyType): str
166
237
  const hash = blake.blake2b(hexToBinUnsafe(publicKey), undefined, 32)
167
238
  const bytes = new Uint8Array([AddressType.P2PKH, ...hash])
168
239
  return bs58.encode(bytes)
240
+ } else if (keyType === 'groupless') {
241
+ const publicKeyBytes = new Uint8Array([0x00, ...hexToBinUnsafe(publicKey)])
242
+ const hashBytes = intAs4BytesCodec.encode(djb2(publicKeyBytes))
243
+ const bytes = new Uint8Array([0x04, ...publicKeyBytes, ...hashBytes])
244
+ return bs58.encode(bytes)
169
245
  } else {
170
246
  const lockupScript = hexToBinUnsafe(`0101000000000458144020${publicKey}8685`)
171
247
  return addressFromScript(lockupScript)
@@ -215,11 +291,13 @@ export function subContractId(parentContractId: string, pathInHex: string, group
215
291
 
216
292
  export function groupOfLockupScript(lockupScript: LockupScript): number {
217
293
  if (lockupScript.kind === 'P2PKH') {
218
- return groupFromBytesForAssetAddress(lockupScript.value)
294
+ return groupFromBytes(lockupScript.value)
219
295
  } else if (lockupScript.kind === 'P2MPKH') {
220
- return groupFromBytesForAssetAddress(lockupScript.value.publicKeyHashes[0])
296
+ return groupFromBytes(lockupScript.value.publicKeyHashes[0])
221
297
  } else if (lockupScript.kind === 'P2SH') {
222
- return groupFromBytesForAssetAddress(lockupScript.value)
298
+ return groupFromBytes(lockupScript.value)
299
+ } else if (lockupScript.kind === 'P2PK') {
300
+ return lockupScript.value.group % TOTAL_NUMBER_OF_GROUPS
223
301
  } else {
224
302
  // P2C
225
303
  const contractId = lockupScript.value
@@ -227,8 +305,38 @@ export function groupOfLockupScript(lockupScript: LockupScript): number {
227
305
  }
228
306
  }
229
307
 
230
- function groupFromBytesForAssetAddress(bytes: Uint8Array): number {
308
+ export function groupFromBytes(bytes: Uint8Array): number {
231
309
  const hint = djb2(bytes) | 1
310
+ return groupFromHint(hint)
311
+ }
312
+
313
+ export function groupFromHint(hint: number): number {
232
314
  const hash = xorByte(hint)
233
315
  return hash % TOTAL_NUMBER_OF_GROUPS
234
316
  }
317
+
318
+ export function hasExplicitGroupIndex(address: string): boolean {
319
+ return address.length > 2 && address[address.length - 2] === ':'
320
+ }
321
+
322
+ export function addressFromLockupScript(lockupScript: LockupScript): string {
323
+ if (lockupScript.kind === 'P2PK') {
324
+ const groupByte = lockupScriptCodec.encode(lockupScript).slice(-1)
325
+ const address = bs58.encode(lockupScriptCodec.encode(lockupScript).slice(0, -1))
326
+ return `${address}:${groupByte}`
327
+ } else {
328
+ return bs58.encode(lockupScriptCodec.encode(lockupScript))
329
+ }
330
+ }
331
+
332
+ function parseGroupIndex(groupIndexStr: string): number {
333
+ return validateGroupIndex(parseInt(groupIndexStr), groupIndexStr)
334
+ }
335
+
336
+ function validateGroupIndex(groupIndex: number, groupIndexStr?: string): number {
337
+ if (isNaN(groupIndex) || groupIndex < 0 || groupIndex >= TOTAL_NUMBER_OF_GROUPS) {
338
+ throw new Error(`Invalid group index: ${groupIndexStr ?? groupIndex}`)
339
+ }
340
+
341
+ return groupIndex
342
+ }