@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.
- package/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/address/address.d.ts +11 -1
- package/dist/src/address/address.js +117 -11
- package/dist/src/api/api-alephium.d.ts +101 -4
- package/dist/src/api/api-alephium.js +51 -1
- package/dist/src/api/node-provider.d.ts +2 -0
- package/dist/src/api/node-provider.js +1 -0
- package/dist/src/api/types.d.ts +1 -1
- package/dist/src/api/types.js +10 -5
- package/dist/src/codec/lockup-script-codec.d.ts +11 -2
- package/dist/src/codec/lockup-script-codec.js +8 -1
- package/dist/src/codec/unlock-script-codec.d.ts +11 -3
- package/dist/src/codec/unlock-script-codec.js +14 -4
- package/dist/src/contract/contract.d.ts +3 -3
- package/dist/src/contract/contract.js +12 -5
- package/dist/src/contract/ralph.js +2 -1
- package/dist/src/signer/signer.js +1 -1
- package/dist/src/signer/tx-builder.d.ts +7 -1
- package/dist/src/signer/tx-builder.js +67 -0
- package/dist/src/signer/types.d.ts +29 -2
- package/dist/src/signer/types.js +3 -0
- package/dist/src/utils/sign.js +2 -2
- package/dist/src/utils/webcrypto.d.ts +3 -1
- package/package.json +3 -3
- package/src/address/address.ts +120 -12
- package/src/api/api-alephium.ts +474 -144
- package/src/api/node-provider.ts +3 -0
- package/src/api/types.ts +10 -5
- package/src/codec/lockup-script-codec.ts +19 -2
- package/src/codec/unlock-script-codec.ts +23 -8
- package/src/contract/contract.ts +33 -10
- package/src/contract/ralph.ts +5 -2
- package/src/signer/signer.ts +1 -1
- package/src/signer/tx-builder.ts +88 -1
- package/src/signer/types.ts +36 -2
- 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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/src/signer/types.js
CHANGED
package/dist/src/utils/sign.js
CHANGED
|
@@ -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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alephium/web3",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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.
|
|
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"
|
package/src/address/address.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
294
|
+
return groupFromBytes(lockupScript.value)
|
|
219
295
|
} else if (lockupScript.kind === 'P2MPKH') {
|
|
220
|
-
return
|
|
296
|
+
return groupFromBytes(lockupScript.value.publicKeyHashes[0])
|
|
221
297
|
} else if (lockupScript.kind === 'P2SH') {
|
|
222
|
-
return
|
|
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
|
|
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
|
+
}
|