@alephium/web3 0.21.2 → 0.23.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.
@@ -57,10 +57,46 @@ class NodeProvider {
57
57
  this.fetchNFTMetaData = async (tokenId) => {
58
58
  const address = (0, utils_1.addressFromTokenId)(tokenId);
59
59
  const group = (0, utils_1.groupOfAddress)(address);
60
- const result = await this.contracts.postContractsCallContract({ methodIndex: 0, group, address });
61
- return {
62
- tokenUri: (0, utils_1.hexToString)((0, contract_1.tryGetCallResult)(result).returns[0].value)
63
- };
60
+ const calls = Array.from([0, 1], (index) => ({ methodIndex: index, group: group, address: address }));
61
+ const result = await this.contracts.postContractsMulticallContract({
62
+ calls: calls
63
+ });
64
+ const tokenUri = (0, utils_1.hexToString)((0, contract_1.tryGetCallResult)(result.results[0]).returns[0].value);
65
+ const collectionIndexResult = result.results[1];
66
+ if (collectionIndexResult.type === 'CallContractSucceeded') {
67
+ const successfulCollectionIndexResult = result.results[1];
68
+ const contractIdReturnResult = successfulCollectionIndexResult.returns[0];
69
+ if (contractIdReturnResult === undefined) {
70
+ throw new Error('Deprecated NFT contract');
71
+ }
72
+ const collectionId = successfulCollectionIndexResult.returns[0].value;
73
+ if (collectionId === undefined || !(0, utils_1.isHexString)(collectionId) || collectionId.length !== 64) {
74
+ throw new Error('Deprecated NFT contract');
75
+ }
76
+ const nftIndexReturnResult = successfulCollectionIndexResult.returns[1];
77
+ if (nftIndexReturnResult === undefined) {
78
+ throw new Error('Deprecated NFT contract');
79
+ }
80
+ const nftIndex = (0, utils_1.toNonNegativeBigInt)(nftIndexReturnResult.value);
81
+ if (nftIndex === undefined) {
82
+ throw new Error('Deprecated NFT contract');
83
+ }
84
+ // If there are more return values, it is also a deprecated NFT contract
85
+ const thirdResult = successfulCollectionIndexResult.returns[2];
86
+ if (thirdResult !== undefined) {
87
+ throw new Error('Deprecated NFT contract');
88
+ }
89
+ return { tokenUri, collectionId, nftIndex };
90
+ }
91
+ else {
92
+ const failedCollectionIndexResult = result.results[1];
93
+ if (failedCollectionIndexResult.error.startsWith('VM execution error: InvalidMethodIndex')) {
94
+ throw new Error('Deprecated NFT contract');
95
+ }
96
+ else {
97
+ throw new Error(`Failed to call contract, error: ${failedCollectionIndexResult.error}`);
98
+ }
99
+ }
64
100
  };
65
101
  // Only use this when the contract follows the NFT collection interface, check `guessFollowsNFTCollectionStd` first
66
102
  this.fetchNFTCollectionMetaData = async (contractId) => {
@@ -38,13 +38,15 @@ export declare enum StdInterfaceIds {
38
38
  NFTCollectionWithRoyalty = "000201"
39
39
  }
40
40
  export interface FungibleTokenMetaData {
41
- name: string;
42
41
  symbol: string;
42
+ name: string;
43
43
  decimals: number;
44
44
  totalSupply: Number256;
45
45
  }
46
46
  export interface NFTMetaData {
47
47
  tokenUri: string;
48
+ collectionId: string;
49
+ nftIndex: Number256;
48
50
  }
49
51
  export interface NFTCollectionMetaData {
50
52
  collectionUri: string;
@@ -556,9 +556,9 @@ class Contract extends Artifact {
556
556
  const fields = this.stdInterfaceId === undefined
557
557
  ? this.fieldsSig
558
558
  : {
559
- names: this.fieldsSig.names.slice(-1),
560
- types: this.fieldsSig.types.slice(-1),
561
- isMutable: this.fieldsSig.isMutable.slice(-1)
559
+ names: this.fieldsSig.names.slice(0, -1),
560
+ types: this.fieldsSig.types.slice(0, -1),
561
+ isMutable: this.fieldsSig.isMutable.slice(0, -1)
562
562
  };
563
563
  return fields.names.reduce((acc, key, index) => {
564
564
  acc[`${key}`] = (0, api_1.getDefaultValue)(fields.types[`${index}`]);
@@ -0,0 +1,7 @@
1
+ import { Transaction } from '../api/api-alephium';
2
+ import { Address } from '../signer';
3
+ export declare function isExchangeAddress(address: string): boolean;
4
+ export declare function isDepositALPHTransaction(tx: Transaction, exchangeAddress: string): boolean;
5
+ export declare function isDepositTokenTransaction(tx: Transaction, exchangeAddress: string): boolean;
6
+ export declare function getDepositAddress(tx: Transaction): Address;
7
+ export declare function getAddressFromUnlockScript(unlockScript: string): Address;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /*
3
+ Copyright 2018 - 2022 The Alephium Authors
4
+ This file is part of the alephium project.
5
+
6
+ The library is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ The library is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with the library. If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.getAddressFromUnlockScript = exports.getDepositAddress = exports.isDepositTokenTransaction = exports.isDepositALPHTransaction = exports.isExchangeAddress = void 0;
21
+ const __1 = require("..");
22
+ function isExchangeAddress(address) {
23
+ const decoded = __1.bs58.decode(address);
24
+ if (decoded.length === 0)
25
+ throw new Error('Address is empty');
26
+ const addressType = decoded[0];
27
+ return (addressType === __1.AddressType.P2PKH || addressType === __1.AddressType.P2SH) && decoded.length === 33;
28
+ }
29
+ exports.isExchangeAddress = isExchangeAddress;
30
+ function isDepositALPHTransaction(tx, exchangeAddress) {
31
+ return isDepositTransaction(tx, exchangeAddress) && checkALPHOutput(tx);
32
+ }
33
+ exports.isDepositALPHTransaction = isDepositALPHTransaction;
34
+ function isDepositTokenTransaction(tx, exchangeAddress) {
35
+ return isDepositTransaction(tx, exchangeAddress) && checkTokenOutput(tx, exchangeAddress);
36
+ }
37
+ exports.isDepositTokenTransaction = isDepositTokenTransaction;
38
+ // we assume that the tx is deposit transaction
39
+ function getDepositAddress(tx) {
40
+ return getAddressFromUnlockScript(tx.unsigned.inputs[0].unlockScript);
41
+ }
42
+ exports.getDepositAddress = getDepositAddress;
43
+ var UnlockScriptType;
44
+ (function (UnlockScriptType) {
45
+ UnlockScriptType[UnlockScriptType["P2PKH"] = 0] = "P2PKH";
46
+ UnlockScriptType[UnlockScriptType["P2MPKH"] = 1] = "P2MPKH";
47
+ UnlockScriptType[UnlockScriptType["P2SH"] = 2] = "P2SH";
48
+ })(UnlockScriptType || (UnlockScriptType = {}));
49
+ function getAddressFromUnlockScript(unlockScript) {
50
+ const decoded = (0, __1.hexToBinUnsafe)(unlockScript);
51
+ if (decoded.length === 0)
52
+ throw new Error('UnlockScript is empty');
53
+ const unlockScriptType = decoded[0];
54
+ const unlockScriptBody = decoded.slice(1);
55
+ if (unlockScriptType === UnlockScriptType.P2PKH) {
56
+ return (0, __1.addressFromPublicKey)((0, __1.binToHex)(unlockScriptBody));
57
+ }
58
+ else if (unlockScriptType === UnlockScriptType.P2MPKH) {
59
+ throw new Error('Naive multi-sig address is not supported for exchanges as it will be replaced by P2SH');
60
+ }
61
+ else if (unlockScriptType === UnlockScriptType.P2SH) {
62
+ // FIXEME: for now we assume that the params is empty, so we need to
63
+ // remove the last byte from the `unlockScriptBody`, we can decode
64
+ // the unlock script once the codec PR is merged
65
+ const script = unlockScriptBody.slice(0, -1);
66
+ return (0, __1.addressFromScript)(script);
67
+ }
68
+ else {
69
+ throw new Error('Invalid unlock script type');
70
+ }
71
+ }
72
+ exports.getAddressFromUnlockScript = getAddressFromUnlockScript;
73
+ function getFromAddress(tx) {
74
+ try {
75
+ const inputAddresses = tx.unsigned.inputs.map((i) => getAddressFromUnlockScript(i.unlockScript));
76
+ // we have checked that the inputs is not empty
77
+ const from = inputAddresses[0];
78
+ return inputAddresses.slice(1).every((addr) => addr === from) ? from : undefined;
79
+ }
80
+ catch (_) {
81
+ return undefined;
82
+ }
83
+ }
84
+ function checkOutputAddress(tx, from, to) {
85
+ let fromCount = 0;
86
+ let toCount = 0;
87
+ tx.unsigned.fixedOutputs.forEach((o) => {
88
+ if (o.address === from) {
89
+ fromCount += 1;
90
+ }
91
+ else if (o.address === to) {
92
+ toCount += 1;
93
+ }
94
+ });
95
+ const outputCount = tx.unsigned.fixedOutputs.length;
96
+ return toCount === 1 && fromCount === outputCount - 1;
97
+ }
98
+ function checkALPHOutput(tx) {
99
+ const outputs = tx.unsigned.fixedOutputs;
100
+ return outputs.every((o) => o.tokens.length === 0);
101
+ }
102
+ function checkTokenOutput(tx, to) {
103
+ // we have checked the output address
104
+ const output = tx.unsigned.fixedOutputs.find((o) => o.address === to);
105
+ return output.attoAlphAmount === __1.DUST_AMOUNT.toString() && output.tokens.length === 1;
106
+ }
107
+ function isDepositTransaction(tx, exchangeAddress) {
108
+ if (tx.contractInputs.length !== 0 ||
109
+ tx.generatedOutputs.length !== 0 ||
110
+ tx.unsigned.inputs.length === 0 ||
111
+ tx.unsigned.scriptOpt !== undefined) {
112
+ return false;
113
+ }
114
+ const from = getFromAddress(tx);
115
+ if (from === undefined) {
116
+ return false;
117
+ }
118
+ return checkOutputAddress(tx, from, exchangeAddress);
119
+ }
@@ -6,3 +6,4 @@ export * from './utils';
6
6
  export * from './subscription';
7
7
  export * from './sign';
8
8
  export * from './number';
9
+ export { isExchangeAddress, isDepositALPHTransaction, isDepositTokenTransaction, getDepositAddress } from './exchange';
@@ -31,6 +31,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
31
31
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
32
32
  };
33
33
  Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.getDepositAddress = exports.isDepositTokenTransaction = exports.isDepositALPHTransaction = exports.isExchangeAddress = void 0;
34
35
  __exportStar(require("./webcrypto"), exports);
35
36
  __exportStar(require("./address"), exports);
36
37
  __exportStar(require("./bs58"), exports);
@@ -39,3 +40,8 @@ __exportStar(require("./utils"), exports);
39
40
  __exportStar(require("./subscription"), exports);
40
41
  __exportStar(require("./sign"), exports);
41
42
  __exportStar(require("./number"), exports);
43
+ var exchange_1 = require("./exchange");
44
+ Object.defineProperty(exports, "isExchangeAddress", { enumerable: true, get: function () { return exchange_1.isExchangeAddress; } });
45
+ Object.defineProperty(exports, "isDepositALPHTransaction", { enumerable: true, get: function () { return exchange_1.isDepositALPHTransaction; } });
46
+ Object.defineProperty(exports, "isDepositTokenTransaction", { enumerable: true, get: function () { return exchange_1.isDepositTokenTransaction; } });
47
+ Object.defineProperty(exports, "getDepositAddress", { enumerable: true, get: function () { return exchange_1.getDepositAddress; } });
@@ -12,6 +12,13 @@ export declare function encodeHexSignature(rHex: string, sHex: string): string;
12
12
  export declare function signatureDecode(ec: EC, signature: string): SignatureInput;
13
13
  export declare function xorByte(intValue: number): number;
14
14
  export declare function isHexString(input: string): boolean;
15
+ export declare function toNonNegativeBigInt(input: string): bigint | undefined;
16
+ export declare enum AddressType {
17
+ P2PKH = 0,
18
+ P2MPKH = 1,
19
+ P2SH = 2,
20
+ P2C = 3
21
+ }
15
22
  export declare function groupOfAddress(address: string): number;
16
23
  export declare function contractIdFromAddress(address: string): Uint8Array;
17
24
  export declare function tokenIdFromAddress(address: string): Uint8Array;
@@ -20,6 +27,7 @@ export declare function binToHex(bin: Uint8Array): string;
20
27
  export declare function groupOfPrivateKey(privateKey: string, keyType?: KeyType): number;
21
28
  export declare function publicKeyFromPrivateKey(privateKey: string, _keyType?: KeyType): string;
22
29
  export declare function addressFromPublicKey(publicKey: string, _keyType?: KeyType): string;
30
+ export declare function addressFromScript(script: Uint8Array): string;
23
31
  export declare function addressFromContractId(contractId: string): string;
24
32
  export declare function addressFromTokenId(tokenId: string): string;
25
33
  export declare function contractIdFromTx(txId: string, outputIndex: number): string;
@@ -20,7 +20,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
20
20
  return (mod && mod.__esModule) ? mod : { "default": mod };
21
21
  };
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.assertType = exports.sleep = exports.hexToString = exports.stringToHex = exports.blockChainIndex = exports.subContractId = exports.contractIdFromTx = exports.addressFromTokenId = exports.addressFromContractId = exports.addressFromPublicKey = exports.publicKeyFromPrivateKey = exports.groupOfPrivateKey = exports.binToHex = exports.hexToBinUnsafe = exports.tokenIdFromAddress = exports.contractIdFromAddress = exports.groupOfAddress = exports.isHexString = exports.xorByte = exports.signatureDecode = exports.encodeHexSignature = exports.encodeSignature = exports.networkIds = void 0;
23
+ exports.assertType = exports.sleep = exports.hexToString = exports.stringToHex = exports.blockChainIndex = exports.subContractId = exports.contractIdFromTx = exports.addressFromTokenId = exports.addressFromContractId = exports.addressFromScript = exports.addressFromPublicKey = exports.publicKeyFromPrivateKey = exports.groupOfPrivateKey = exports.binToHex = exports.hexToBinUnsafe = exports.tokenIdFromAddress = exports.contractIdFromAddress = exports.groupOfAddress = exports.AddressType = exports.toNonNegativeBigInt = exports.isHexString = exports.xorByte = exports.signatureDecode = exports.encodeHexSignature = exports.encodeSignature = exports.networkIds = void 0;
24
24
  const elliptic_1 = require("elliptic");
25
25
  const bn_js_1 = __importDefault(require("bn.js"));
26
26
  const blakejs_1 = __importDefault(require("blakejs"));
@@ -72,13 +72,23 @@ function isHexString(input) {
72
72
  return input.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(input);
73
73
  }
74
74
  exports.isHexString = isHexString;
75
+ function toNonNegativeBigInt(input) {
76
+ try {
77
+ const bigIntValue = BigInt(input);
78
+ return bigIntValue < 0n ? undefined : bigIntValue;
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
84
+ exports.toNonNegativeBigInt = toNonNegativeBigInt;
75
85
  var AddressType;
76
86
  (function (AddressType) {
77
87
  AddressType[AddressType["P2PKH"] = 0] = "P2PKH";
78
88
  AddressType[AddressType["P2MPKH"] = 1] = "P2MPKH";
79
89
  AddressType[AddressType["P2SH"] = 2] = "P2SH";
80
90
  AddressType[AddressType["P2C"] = 3] = "P2C";
81
- })(AddressType || (AddressType = {}));
91
+ })(AddressType = exports.AddressType || (exports.AddressType = {}));
82
92
  function groupOfAddress(address) {
83
93
  const decoded = bs58_1.default.decode(address);
84
94
  if (decoded.length == 0)
@@ -179,12 +189,16 @@ function addressFromPublicKey(publicKey, _keyType) {
179
189
  }
180
190
  else {
181
191
  const lockupScript = buffer_1.Buffer.from(`0101000000000458144020${publicKey}8685`, 'hex');
182
- const lockupScriptHash = blakejs_1.default.blake2b(lockupScript, undefined, 32);
183
- const addressType = buffer_1.Buffer.from([AddressType.P2SH]);
184
- return bs58_1.default.encode(buffer_1.Buffer.concat([addressType, lockupScriptHash]));
192
+ return addressFromScript(lockupScript);
185
193
  }
186
194
  }
187
195
  exports.addressFromPublicKey = addressFromPublicKey;
196
+ function addressFromScript(script) {
197
+ const scriptHash = blakejs_1.default.blake2b(script, undefined, 32);
198
+ const addressType = buffer_1.Buffer.from([AddressType.P2SH]);
199
+ return bs58_1.default.encode(buffer_1.Buffer.concat([addressType, scriptHash]));
200
+ }
201
+ exports.addressFromScript = addressFromScript;
188
202
  function addressFromContractId(contractId) {
189
203
  const addressType = buffer_1.Buffer.from([AddressType.P2C]);
190
204
  const hash = buffer_1.Buffer.from(hexToBinUnsafe(contractId));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alephium/web3",
3
- "version": "0.21.2",
3
+ "version": "0.23.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",
@@ -82,7 +82,7 @@
82
82
  "webpack-cli": "^4.10.0"
83
83
  },
84
84
  "engines": {
85
- "node": ">=16.0.0",
85
+ "node": ">=14.0.0",
86
86
  "npm": ">=7.0.0"
87
87
  },
88
88
  "scripts": {
@@ -26,9 +26,16 @@ import {
26
26
  NFTCollectionMetaData,
27
27
  StdInterfaceIds
28
28
  } from './types'
29
- import { Api as NodeApi } from './api-alephium'
29
+ import { Api as NodeApi, CallContractFailed, CallContractSucceeded } from './api-alephium'
30
30
  import { HexString, tryGetCallResult } from '../contract'
31
- import { addressFromContractId, addressFromTokenId, groupOfAddress, hexToString } from '../utils'
31
+ import {
32
+ addressFromContractId,
33
+ addressFromTokenId,
34
+ groupOfAddress,
35
+ hexToString,
36
+ isHexString,
37
+ toNonNegativeBigInt
38
+ } from '../utils'
32
39
 
33
40
  function initializeNodeApi(baseUrl: string, apiKey?: string, customFetch?: typeof fetch): NodeApi<string> {
34
41
  const nodeApi = new NodeApi<string>({
@@ -129,9 +136,46 @@ export class NodeProvider implements NodeProviderApis {
129
136
  fetchNFTMetaData = async (tokenId: HexString): Promise<NFTMetaData> => {
130
137
  const address = addressFromTokenId(tokenId)
131
138
  const group = groupOfAddress(address)
132
- const result = await this.contracts.postContractsCallContract({ methodIndex: 0, group, address })
133
- return {
134
- tokenUri: hexToString(tryGetCallResult(result).returns[0].value as any as string)
139
+ const calls = Array.from([0, 1], (index) => ({ methodIndex: index, group: group, address: address }))
140
+ const result = await this.contracts.postContractsMulticallContract({
141
+ calls: calls
142
+ })
143
+ const tokenUri = hexToString(tryGetCallResult(result.results[0]).returns[0].value as any as string)
144
+ const collectionIndexResult = result.results[1]
145
+ if (collectionIndexResult.type === 'CallContractSucceeded') {
146
+ const successfulCollectionIndexResult = result.results[1] as CallContractSucceeded
147
+ const contractIdReturnResult = successfulCollectionIndexResult.returns[0]
148
+ if (contractIdReturnResult === undefined) {
149
+ throw new Error('Deprecated NFT contract')
150
+ }
151
+ const collectionId = successfulCollectionIndexResult.returns[0].value as any as string
152
+ if (collectionId === undefined || !isHexString(collectionId) || collectionId.length !== 64) {
153
+ throw new Error('Deprecated NFT contract')
154
+ }
155
+
156
+ const nftIndexReturnResult = successfulCollectionIndexResult.returns[1]
157
+ if (nftIndexReturnResult === undefined) {
158
+ throw new Error('Deprecated NFT contract')
159
+ }
160
+ const nftIndex = toNonNegativeBigInt(nftIndexReturnResult.value as any as string)
161
+ if (nftIndex === undefined) {
162
+ throw new Error('Deprecated NFT contract')
163
+ }
164
+
165
+ // If there are more return values, it is also a deprecated NFT contract
166
+ const thirdResult = successfulCollectionIndexResult.returns[2]
167
+ if (thirdResult !== undefined) {
168
+ throw new Error('Deprecated NFT contract')
169
+ }
170
+
171
+ return { tokenUri, collectionId, nftIndex }
172
+ } else {
173
+ const failedCollectionIndexResult = result.results[1] as CallContractFailed
174
+ if (failedCollectionIndexResult.error.startsWith('VM execution error: InvalidMethodIndex')) {
175
+ throw new Error('Deprecated NFT contract')
176
+ } else {
177
+ throw new Error(`Failed to call contract, error: ${failedCollectionIndexResult.error}`)
178
+ }
135
179
  }
136
180
  }
137
181
 
package/src/api/types.ts CHANGED
@@ -291,14 +291,16 @@ export enum StdInterfaceIds {
291
291
  }
292
292
 
293
293
  export interface FungibleTokenMetaData {
294
- name: string
295
294
  symbol: string
295
+ name: string
296
296
  decimals: number
297
297
  totalSupply: Number256
298
298
  }
299
299
 
300
300
  export interface NFTMetaData {
301
301
  tokenUri: string
302
+ collectionId: string
303
+ nftIndex: Number256
302
304
  }
303
305
 
304
306
  export interface NFTCollectionMetaData {
@@ -850,9 +850,9 @@ export class Contract extends Artifact {
850
850
  this.stdInterfaceId === undefined
851
851
  ? this.fieldsSig
852
852
  : {
853
- names: this.fieldsSig.names.slice(-1),
854
- types: this.fieldsSig.types.slice(-1),
855
- isMutable: this.fieldsSig.isMutable.slice(-1)
853
+ names: this.fieldsSig.names.slice(0, -1),
854
+ types: this.fieldsSig.types.slice(0, -1),
855
+ isMutable: this.fieldsSig.isMutable.slice(0, -1)
856
856
  }
857
857
  return fields.names.reduce((acc, key, index) => {
858
858
  acc[`${key}`] = getDefaultValue(fields.types[`${index}`])
@@ -0,0 +1,120 @@
1
+ /*
2
+ Copyright 2018 - 2022 The Alephium Authors
3
+ This file is part of the alephium project.
4
+
5
+ The library is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Lesser General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ The library is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with the library. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import { AddressType, DUST_AMOUNT, addressFromPublicKey, addressFromScript, binToHex, bs58, hexToBinUnsafe } from '..'
20
+ import { Transaction } from '../api/api-alephium'
21
+ import { Address } from '../signer'
22
+
23
+ export function isExchangeAddress(address: string): boolean {
24
+ const decoded = bs58.decode(address)
25
+ if (decoded.length === 0) throw new Error('Address is empty')
26
+ const addressType = decoded[0]
27
+ return (addressType === AddressType.P2PKH || addressType === AddressType.P2SH) && decoded.length === 33
28
+ }
29
+
30
+ export function isDepositALPHTransaction(tx: Transaction, exchangeAddress: string): boolean {
31
+ return isDepositTransaction(tx, exchangeAddress) && checkALPHOutput(tx)
32
+ }
33
+
34
+ export function isDepositTokenTransaction(tx: Transaction, exchangeAddress: string): boolean {
35
+ return isDepositTransaction(tx, exchangeAddress) && checkTokenOutput(tx, exchangeAddress)
36
+ }
37
+
38
+ // we assume that the tx is deposit transaction
39
+ export function getDepositAddress(tx: Transaction): Address {
40
+ return getAddressFromUnlockScript(tx.unsigned.inputs[0].unlockScript)
41
+ }
42
+
43
+ enum UnlockScriptType {
44
+ P2PKH = 0x00,
45
+ P2MPKH = 0x01,
46
+ P2SH = 0x02
47
+ }
48
+
49
+ export function getAddressFromUnlockScript(unlockScript: string): Address {
50
+ const decoded = hexToBinUnsafe(unlockScript)
51
+ if (decoded.length === 0) throw new Error('UnlockScript is empty')
52
+ const unlockScriptType = decoded[0]
53
+ const unlockScriptBody = decoded.slice(1)
54
+
55
+ if (unlockScriptType === UnlockScriptType.P2PKH) {
56
+ return addressFromPublicKey(binToHex(unlockScriptBody))
57
+ } else if (unlockScriptType === UnlockScriptType.P2MPKH) {
58
+ throw new Error('Naive multi-sig address is not supported for exchanges as it will be replaced by P2SH')
59
+ } else if (unlockScriptType === UnlockScriptType.P2SH) {
60
+ // FIXEME: for now we assume that the params is empty, so we need to
61
+ // remove the last byte from the `unlockScriptBody`, we can decode
62
+ // the unlock script once the codec PR is merged
63
+ const script = unlockScriptBody.slice(0, -1)
64
+ return addressFromScript(script)
65
+ } else {
66
+ throw new Error('Invalid unlock script type')
67
+ }
68
+ }
69
+
70
+ function getFromAddress(tx: Transaction): Address | undefined {
71
+ try {
72
+ const inputAddresses = tx.unsigned.inputs.map((i) => getAddressFromUnlockScript(i.unlockScript))
73
+ // we have checked that the inputs is not empty
74
+ const from = inputAddresses[0]
75
+ return inputAddresses.slice(1).every((addr) => addr === from) ? from : undefined
76
+ } catch (_) {
77
+ return undefined
78
+ }
79
+ }
80
+
81
+ function checkOutputAddress(tx: Transaction, from: Address, to: Address): boolean {
82
+ let fromCount = 0
83
+ let toCount = 0
84
+ tx.unsigned.fixedOutputs.forEach((o) => {
85
+ if (o.address === from) {
86
+ fromCount += 1
87
+ } else if (o.address === to) {
88
+ toCount += 1
89
+ }
90
+ })
91
+ const outputCount = tx.unsigned.fixedOutputs.length
92
+ return toCount === 1 && fromCount === outputCount - 1
93
+ }
94
+
95
+ function checkALPHOutput(tx: Transaction): boolean {
96
+ const outputs = tx.unsigned.fixedOutputs
97
+ return outputs.every((o) => o.tokens.length === 0)
98
+ }
99
+
100
+ function checkTokenOutput(tx: Transaction, to: Address): boolean {
101
+ // we have checked the output address
102
+ const output = tx.unsigned.fixedOutputs.find((o) => o.address === to)!
103
+ return output.attoAlphAmount === DUST_AMOUNT.toString() && output.tokens.length === 1
104
+ }
105
+
106
+ function isDepositTransaction(tx: Transaction, exchangeAddress: string): boolean {
107
+ if (
108
+ tx.contractInputs.length !== 0 ||
109
+ tx.generatedOutputs.length !== 0 ||
110
+ tx.unsigned.inputs.length === 0 ||
111
+ tx.unsigned.scriptOpt !== undefined
112
+ ) {
113
+ return false
114
+ }
115
+ const from = getFromAddress(tx)
116
+ if (from === undefined) {
117
+ return false
118
+ }
119
+ return checkOutputAddress(tx, from, exchangeAddress)
120
+ }
@@ -24,3 +24,4 @@ export * from './utils'
24
24
  export * from './subscription'
25
25
  export * from './sign'
26
26
  export * from './number'
27
+ export { isExchangeAddress, isDepositALPHTransaction, isDepositTokenTransaction, getDepositAddress } from './exchange'
@@ -75,7 +75,16 @@ export function isHexString(input: string): boolean {
75
75
  return input.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(input)
76
76
  }
77
77
 
78
- enum AddressType {
78
+ export function toNonNegativeBigInt(input: string): bigint | undefined {
79
+ try {
80
+ const bigIntValue = BigInt(input)
81
+ return bigIntValue < 0n ? undefined : bigIntValue
82
+ } catch {
83
+ return undefined
84
+ }
85
+ }
86
+
87
+ export enum AddressType {
79
88
  P2PKH = 0x00,
80
89
  P2MPKH = 0x01,
81
90
  P2SH = 0x02,
@@ -187,12 +196,16 @@ export function addressFromPublicKey(publicKey: string, _keyType?: KeyType): str
187
196
  return bs58.encode(bytes)
188
197
  } else {
189
198
  const lockupScript = Buffer.from(`0101000000000458144020${publicKey}8685`, 'hex')
190
- const lockupScriptHash = blake.blake2b(lockupScript, undefined, 32)
191
- const addressType = Buffer.from([AddressType.P2SH])
192
- return bs58.encode(Buffer.concat([addressType, lockupScriptHash]))
199
+ return addressFromScript(lockupScript)
193
200
  }
194
201
  }
195
202
 
203
+ export function addressFromScript(script: Uint8Array): string {
204
+ const scriptHash = blake.blake2b(script, undefined, 32)
205
+ const addressType = Buffer.from([AddressType.P2SH])
206
+ return bs58.encode(Buffer.concat([addressType, scriptHash]))
207
+ }
208
+
196
209
  export function addressFromContractId(contractId: string): string {
197
210
  const addressType = Buffer.from([AddressType.P2C])
198
211
  const hash = Buffer.from(hexToBinUnsafe(contractId))
@@ -26,4 +26,7 @@ Interface INFTCollection {
26
26
  pub fn totalSupply() -> U256
27
27
 
28
28
  pub fn nftByIndex(index: U256) -> INFT
29
+
30
+ // Validates that the NFT is part of the collection, otherwise throws exception.
31
+ pub fn validateNFT(nftId: ByteVec, nftIndex: U256) -> ()
29
32
  }
@@ -22,4 +22,7 @@ Interface INFT {
22
22
  // }
23
23
  // }
24
24
  pub fn getTokenUri() -> ByteVec
25
+
26
+ // Returns collection id and index of the NFT in the collection.
27
+ pub fn getCollectionIndex() -> (ByteVec, U256)
25
28
  }