@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.
- package/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/api/node-provider.js +40 -4
- package/dist/src/api/types.d.ts +3 -1
- package/dist/src/contract/contract.js +3 -3
- package/dist/src/utils/exchange.d.ts +7 -0
- package/dist/src/utils/exchange.js +119 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.js +6 -0
- package/dist/src/utils/utils.d.ts +8 -0
- package/dist/src/utils/utils.js +19 -5
- package/package.json +2 -2
- package/src/api/node-provider.ts +49 -5
- package/src/api/types.ts +3 -1
- package/src/contract/contract.ts +3 -3
- package/src/utils/exchange.ts +120 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/utils.ts +17 -4
- package/std/nft_collection_interface.ral +3 -0
- package/std/nft_interface.ral +3 -0
|
@@ -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
|
|
61
|
-
|
|
62
|
-
|
|
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) => {
|
package/dist/src/api/types.d.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/src/utils/index.js
CHANGED
|
@@ -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;
|
package/dist/src/utils/utils.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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": ">=
|
|
85
|
+
"node": ">=14.0.0",
|
|
86
86
|
"npm": ">=7.0.0"
|
|
87
87
|
},
|
|
88
88
|
"scripts": {
|
package/src/api/node-provider.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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 {
|
package/src/contract/contract.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/utils.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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))
|