@alephium/web3 1.8.5 → 1.10.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/api-alephium.d.ts +25 -6
- package/dist/src/api/api-alephium.js +33 -4
- package/dist/src/api/api-explorer.d.ts +810 -819
- package/dist/src/api/api-explorer.js +350 -360
- package/dist/src/codec/instr-codec.d.ts +2 -0
- package/dist/src/codec/instr-codec.js +54 -1
- package/dist/src/codec/unlock-script-codec.d.ts +1 -0
- package/dist/src/codec/unlock-script-codec.js +2 -1
- package/dist/src/constants.d.ts +4 -0
- package/dist/src/constants.js +5 -1
- package/dist/src/contract/contract.d.ts +5 -1
- package/dist/src/contract/contract.js +44 -8
- package/dist/src/contract/dapp-tx-builder.d.ts +26 -0
- package/dist/src/contract/dapp-tx-builder.js +187 -0
- package/dist/src/contract/events.d.ts +1 -0
- package/dist/src/contract/events.js +14 -3
- package/dist/src/contract/index.d.ts +1 -0
- package/dist/src/contract/index.js +3 -0
- package/dist/src/contract/ralph.js +2 -34
- package/dist/src/exchange/exchange.d.ts +13 -2
- package/dist/src/exchange/exchange.js +52 -14
- package/dist/src/exchange/index.d.ts +1 -1
- package/dist/src/exchange/index.js +3 -2
- package/dist/src/signer/types.d.ts +1 -0
- package/package.json +5 -5
- package/src/api/api-alephium.ts +49 -9
- package/src/api/api-explorer.ts +990 -1000
- package/src/codec/instr-codec.ts +56 -1
- package/src/codec/unlock-script-codec.ts +2 -0
- package/src/constants.ts +4 -0
- package/src/contract/contract.ts +46 -7
- package/src/contract/dapp-tx-builder.ts +209 -0
- package/src/contract/events.ts +15 -3
- package/src/contract/index.ts +1 -0
- package/src/contract/ralph.ts +4 -49
- package/src/exchange/exchange.ts +69 -17
- package/src/exchange/index.ts +10 -1
- package/src/signer/tx-builder.ts +2 -2
- package/src/signer/types.ts +1 -0
package/src/codec/instr-codec.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { ArrayCodec } from './array-codec'
|
|
|
20
20
|
import { i256Codec, u256Codec, i32Codec } from './compact-int-codec'
|
|
21
21
|
import { ByteString, byteStringCodec, byteStringsCodec } from './bytestring-codec'
|
|
22
22
|
import { LockupScript, lockupScriptCodec } from './lockup-script-codec'
|
|
23
|
-
import { byteCodec, Codec } from './codec'
|
|
23
|
+
import { assert, byteCodec, Codec } from './codec'
|
|
24
24
|
import { intAs4BytesCodec } from './int-as-4bytes-codec'
|
|
25
25
|
import { Reader } from './reader'
|
|
26
26
|
export type Instr =
|
|
@@ -1263,3 +1263,58 @@ export class InstrCodec extends Codec<Instr> {
|
|
|
1263
1263
|
}
|
|
1264
1264
|
export const instrCodec = new InstrCodec()
|
|
1265
1265
|
export const instrsCodec = new ArrayCodec<Instr>(instrCodec)
|
|
1266
|
+
|
|
1267
|
+
function checkU256(number: bigint) {
|
|
1268
|
+
if (number < 0n || number >= 2n ** 256n) {
|
|
1269
|
+
throw new Error(`Invalid u256 number: ${number}`)
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
export function toU256(number: bigint) {
|
|
1274
|
+
checkU256(number)
|
|
1275
|
+
switch (number) {
|
|
1276
|
+
case 0n:
|
|
1277
|
+
return U256Const0
|
|
1278
|
+
case 1n:
|
|
1279
|
+
return U256Const1
|
|
1280
|
+
case 2n:
|
|
1281
|
+
return U256Const2
|
|
1282
|
+
case 3n:
|
|
1283
|
+
return U256Const3
|
|
1284
|
+
case 4n:
|
|
1285
|
+
return U256Const4
|
|
1286
|
+
case 5n:
|
|
1287
|
+
return U256Const5
|
|
1288
|
+
default:
|
|
1289
|
+
return U256Const(number)
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function checkI256(number: bigint) {
|
|
1294
|
+
const upperBound = 2n ** 255n
|
|
1295
|
+
if (number < -upperBound || number >= upperBound) {
|
|
1296
|
+
throw new Error(`Invalid i256 number: ${number}`)
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export function toI256(number: bigint) {
|
|
1301
|
+
checkI256(number)
|
|
1302
|
+
switch (number) {
|
|
1303
|
+
case 0n:
|
|
1304
|
+
return I256Const0
|
|
1305
|
+
case 1n:
|
|
1306
|
+
return I256Const1
|
|
1307
|
+
case 2n:
|
|
1308
|
+
return I256Const2
|
|
1309
|
+
case 3n:
|
|
1310
|
+
return I256Const3
|
|
1311
|
+
case 4n:
|
|
1312
|
+
return I256Const4
|
|
1313
|
+
case 5n:
|
|
1314
|
+
return I256Const5
|
|
1315
|
+
case -1n:
|
|
1316
|
+
return I256ConstN1
|
|
1317
|
+
default:
|
|
1318
|
+
return I256Const(number)
|
|
1319
|
+
}
|
|
1320
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -22,7 +22,11 @@ export const MIN_UTXO_SET_AMOUNT = BigInt(1000000000000)
|
|
|
22
22
|
export const ALPH_TOKEN_ID = ''.padStart(64, '0')
|
|
23
23
|
export const ONE_ALPH = 10n ** 18n
|
|
24
24
|
export const DUST_AMOUNT = 10n ** 15n
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated `ZERO_ADDRESS` is deprecated. Use `NULL_CONTRACT_ADDRESS` instead.
|
|
27
|
+
*/
|
|
25
28
|
export const ZERO_ADDRESS = 'tgx7VNFoP9DJiFMFgXXtafQZkUvyEdDHT9ryamHJYrjq'
|
|
29
|
+
export const NULL_CONTRACT_ADDRESS = 'tgx7VNFoP9DJiFMFgXXtafQZkUvyEdDHT9ryamHJYrjq'
|
|
26
30
|
export const DEFAULT_GAS_AMOUNT = 20000
|
|
27
31
|
export const DEFAULT_GAS_PRICE = 10n ** 11n
|
|
28
32
|
export const DEFAULT_GAS_ATTOALPH_AMOUNT = BigInt(DEFAULT_GAS_AMOUNT) * DEFAULT_GAS_PRICE
|
package/src/contract/contract.ts
CHANGED
|
@@ -204,6 +204,7 @@ export class Contract extends Artifact {
|
|
|
204
204
|
readonly decodedContract: contract.Contract
|
|
205
205
|
|
|
206
206
|
private bytecodeForTesting: string | undefined
|
|
207
|
+
private decodedTestingContract: contract.Contract | undefined
|
|
207
208
|
private codeHashForTesting: string | undefined
|
|
208
209
|
|
|
209
210
|
constructor(
|
|
@@ -239,13 +240,23 @@ export class Contract extends Artifact {
|
|
|
239
240
|
|
|
240
241
|
this.decodedContract = contract.contractCodec.decodeContract(hexToBinUnsafe(this.bytecode))
|
|
241
242
|
this.bytecodeForTesting = undefined
|
|
243
|
+
this.decodedTestingContract = undefined
|
|
242
244
|
this.codeHashForTesting = undefined
|
|
243
245
|
}
|
|
244
246
|
|
|
247
|
+
isInlineFunc(index: number): boolean {
|
|
248
|
+
if (index >= this.functions.length) {
|
|
249
|
+
throw new Error(`Invalid function index ${index}, function size: ${this.functions.length}`)
|
|
250
|
+
}
|
|
251
|
+
const inlineFuncFromIndex = this.decodedContract.methods.length
|
|
252
|
+
return index >= inlineFuncFromIndex
|
|
253
|
+
}
|
|
254
|
+
|
|
245
255
|
getByteCodeForTesting(): string {
|
|
246
256
|
if (this.bytecodeForTesting !== undefined) return this.bytecodeForTesting
|
|
247
257
|
|
|
248
|
-
|
|
258
|
+
const hasInlineFunction = this.functions.length > this.decodedContract.methods.length
|
|
259
|
+
if (!hasInlineFunction && this.publicFunctions().length == this.functions.length) {
|
|
249
260
|
this.bytecodeForTesting = this.bytecodeDebug
|
|
250
261
|
this.codeHashForTesting = this.codeHashDebug
|
|
251
262
|
return this.bytecodeForTesting
|
|
@@ -263,6 +274,13 @@ export class Contract extends Artifact {
|
|
|
263
274
|
return this.bytecodeForTesting
|
|
264
275
|
}
|
|
265
276
|
|
|
277
|
+
getDecodedTestingContract() {
|
|
278
|
+
if (this.decodedTestingContract !== undefined) return this.decodedTestingContract
|
|
279
|
+
const bytecodeForTesting = hexToBinUnsafe(this.getByteCodeForTesting())
|
|
280
|
+
this.decodedTestingContract = contract.contractCodec.decodeContract(bytecodeForTesting)
|
|
281
|
+
return this.decodedTestingContract
|
|
282
|
+
}
|
|
283
|
+
|
|
266
284
|
hasCodeHash(hash: string): boolean {
|
|
267
285
|
return this.codeHash === hash || this.codeHashDebug === hash || this.codeHashForTesting === hash
|
|
268
286
|
}
|
|
@@ -283,8 +301,10 @@ export class Contract extends Artifact {
|
|
|
283
301
|
return this.functions.filter((_, index) => this.getDecodedMethod(index).useContractAssets)
|
|
284
302
|
}
|
|
285
303
|
|
|
286
|
-
isMethodUsePreapprovedAssets(methodIndex: number): boolean {
|
|
287
|
-
return this.getDecodedMethod(methodIndex).usePreapprovedAssets
|
|
304
|
+
isMethodUsePreapprovedAssets(isDevnet: boolean, methodIndex: number): boolean {
|
|
305
|
+
if (!isDevnet || !this.isInlineFunc(methodIndex)) return this.getDecodedMethod(methodIndex).usePreapprovedAssets
|
|
306
|
+
const contract = this.getDecodedTestingContract()
|
|
307
|
+
return contract.methods[`${methodIndex}`].usePreapprovedAssets
|
|
288
308
|
}
|
|
289
309
|
|
|
290
310
|
// TODO: safely parse json
|
|
@@ -455,6 +475,7 @@ export class Contract extends Artifact {
|
|
|
455
475
|
)
|
|
456
476
|
const immFields = allFields.filter((f) => !f.isMutable).map((f) => toApiVal(f.value, f.type))
|
|
457
477
|
const mutFields = allFields.filter((f) => f.isMutable).map((f) => toApiVal(f.value, f.type))
|
|
478
|
+
const methodIndex = this.getMethodIndex(funcName)
|
|
458
479
|
return {
|
|
459
480
|
group: params.group,
|
|
460
481
|
blockHash: params.blockHash,
|
|
@@ -462,11 +483,11 @@ export class Contract extends Artifact {
|
|
|
462
483
|
txId: params.txId,
|
|
463
484
|
address: params.address,
|
|
464
485
|
callerAddress: params.callerAddress,
|
|
465
|
-
bytecode: this.bytecodeDebug,
|
|
486
|
+
bytecode: this.isInlineFunc(methodIndex) ? this.getByteCodeForTesting() : this.bytecodeDebug,
|
|
466
487
|
initialImmFields: immFields,
|
|
467
488
|
initialMutFields: mutFields,
|
|
468
489
|
initialAsset: typeof params.initialAsset !== 'undefined' ? toApiAsset(params.initialAsset) : undefined,
|
|
469
|
-
methodIndex
|
|
490
|
+
methodIndex,
|
|
470
491
|
args: this.toApiArgs(funcName, params.testArgs),
|
|
471
492
|
existingContracts: this.toApiContractStates(params.existingContracts),
|
|
472
493
|
inputAssets: toApiInputAssets(params.inputAssets)
|
|
@@ -1851,7 +1872,8 @@ export async function signExecuteMethod<I extends ContractInstance, F extends Fi
|
|
|
1851
1872
|
): Promise<SignExecuteScriptTxResult> {
|
|
1852
1873
|
const methodIndex = contract.contract.getMethodIndex(methodName)
|
|
1853
1874
|
const functionSig = contract.contract.functions[methodIndex]
|
|
1854
|
-
const
|
|
1875
|
+
const isDevnet = await contract.contract.isDevnet(params.signer)
|
|
1876
|
+
const methodUsePreapprovedAssets = contract.contract.isMethodUsePreapprovedAssets(isDevnet, methodIndex)
|
|
1855
1877
|
const bytecodeTemplate = getBytecodeTemplate(
|
|
1856
1878
|
methodIndex,
|
|
1857
1879
|
methodUsePreapprovedAssets,
|
|
@@ -1882,7 +1904,7 @@ export async function signExecuteMethod<I extends ContractInstance, F extends Fi
|
|
|
1882
1904
|
}
|
|
1883
1905
|
|
|
1884
1906
|
const result = await signer.signAndSubmitExecuteScriptTx(signerParams)
|
|
1885
|
-
if (isContractDebugMessageEnabled() &&
|
|
1907
|
+
if (isContractDebugMessageEnabled() && isDevnet) {
|
|
1886
1908
|
await printDebugMessagesFromTx(result.txId, signer.nodeProvider)
|
|
1887
1909
|
}
|
|
1888
1910
|
return result
|
|
@@ -2159,3 +2181,20 @@ export const getContractIdFromUnsignedTx = async (
|
|
|
2159
2181
|
|
|
2160
2182
|
// This function only works in the simple case where a single non-subcontract is created in the tx
|
|
2161
2183
|
export const getTokenIdFromUnsignedTx = getContractIdFromUnsignedTx
|
|
2184
|
+
|
|
2185
|
+
export async function getContractCodeByCodeHash(
|
|
2186
|
+
nodeProvider: NodeProvider,
|
|
2187
|
+
codeHash: HexString
|
|
2188
|
+
): Promise<HexString | undefined> {
|
|
2189
|
+
if (isHexString(codeHash) && codeHash.length === 64) {
|
|
2190
|
+
try {
|
|
2191
|
+
return await nodeProvider.contracts.getContractsCodehashCode(codeHash)
|
|
2192
|
+
} catch (error) {
|
|
2193
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
2194
|
+
return undefined
|
|
2195
|
+
}
|
|
2196
|
+
throw new TraceableError(`Failed to get contract by code hash ${codeHash}`, error)
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
throw new Error(`Invalid code hash: ${codeHash}`)
|
|
2200
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
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 { contractIdFromAddress, isContractAddress } from '../address'
|
|
20
|
+
import { Val } from '../api'
|
|
21
|
+
import {
|
|
22
|
+
AddressConst,
|
|
23
|
+
ApproveAlph,
|
|
24
|
+
ApproveToken,
|
|
25
|
+
BytesConst,
|
|
26
|
+
CallExternal,
|
|
27
|
+
ConstFalse,
|
|
28
|
+
ConstTrue,
|
|
29
|
+
Dup,
|
|
30
|
+
Instr,
|
|
31
|
+
Pop,
|
|
32
|
+
toI256,
|
|
33
|
+
toU256,
|
|
34
|
+
U256Const,
|
|
35
|
+
Method
|
|
36
|
+
} from '../codec'
|
|
37
|
+
import { LockupScript, lockupScriptCodec } from '../codec/lockup-script-codec'
|
|
38
|
+
import { scriptCodec } from '../codec/script-codec'
|
|
39
|
+
import { ALPH_TOKEN_ID } from '../constants'
|
|
40
|
+
import { TraceableError } from '../error'
|
|
41
|
+
import { SignExecuteScriptTxParams } from '../signer'
|
|
42
|
+
import { base58ToBytes, binToHex, HexString, hexToBinUnsafe, isBase58, isHexString } from '../utils'
|
|
43
|
+
|
|
44
|
+
export class DappTransactionBuilder {
|
|
45
|
+
private callerLockupScript: LockupScript
|
|
46
|
+
private approvedAssets: Map<HexString, bigint>
|
|
47
|
+
private instrs: Instr[]
|
|
48
|
+
|
|
49
|
+
constructor(public readonly callerAddress: string) {
|
|
50
|
+
try {
|
|
51
|
+
this.callerLockupScript = lockupScriptCodec.decode(base58ToBytes(this.callerAddress))
|
|
52
|
+
if (this.callerLockupScript.kind !== 'P2PKH' && this.callerLockupScript.kind !== 'P2SH') {
|
|
53
|
+
throw new Error(`Expected a P2PKH address or P2SH address`)
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new TraceableError(`Invalid caller address: ${callerAddress}`, error)
|
|
57
|
+
}
|
|
58
|
+
this.approvedAssets = new Map<HexString, bigint>()
|
|
59
|
+
this.instrs = []
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
callContract(params: {
|
|
63
|
+
contractAddress: string
|
|
64
|
+
methodIndex: number
|
|
65
|
+
args: Val[]
|
|
66
|
+
attoAlphAmount?: bigint
|
|
67
|
+
tokens?: { id: HexString; amount: bigint }[]
|
|
68
|
+
retLength?: number
|
|
69
|
+
}) {
|
|
70
|
+
if (!isBase58(params.contractAddress)) {
|
|
71
|
+
throw new Error(`Invalid contract address: ${params.contractAddress}, expected a base58 string`)
|
|
72
|
+
}
|
|
73
|
+
if (!isContractAddress(params.contractAddress)) {
|
|
74
|
+
throw new Error(`Invalid contract address: ${params.contractAddress}, expected a P2C address`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (params.methodIndex < 0) {
|
|
78
|
+
throw new Error(`Invalid method index: ${params.methodIndex}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const allTokens = (params.tokens ?? []).concat([{ id: ALPH_TOKEN_ID, amount: params.attoAlphAmount ?? 0n }])
|
|
82
|
+
const instrs = [
|
|
83
|
+
...genApproveAssets(this.callerLockupScript, this.approveTokens(allTokens)),
|
|
84
|
+
...genContractCall(params.contractAddress, params.methodIndex, params.args, params.retLength ?? 0)
|
|
85
|
+
]
|
|
86
|
+
this.instrs.push(...instrs)
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getResult(): SignExecuteScriptTxParams {
|
|
91
|
+
const method: Method = {
|
|
92
|
+
isPublic: true,
|
|
93
|
+
usePreapprovedAssets: this.approvedAssets.size > 0,
|
|
94
|
+
useContractAssets: false,
|
|
95
|
+
usePayToContractOnly: false,
|
|
96
|
+
argsLength: 0,
|
|
97
|
+
localsLength: 0,
|
|
98
|
+
returnLength: 0,
|
|
99
|
+
instrs: this.instrs
|
|
100
|
+
}
|
|
101
|
+
const script = { methods: [method] }
|
|
102
|
+
const bytecode = scriptCodec.encode(script)
|
|
103
|
+
const tokens = Array.from(this.approvedAssets.entries()).map(([id, amount]) => ({ id, amount }))
|
|
104
|
+
this.approvedAssets.clear()
|
|
105
|
+
this.instrs = []
|
|
106
|
+
return {
|
|
107
|
+
signerAddress: this.callerAddress,
|
|
108
|
+
signerKeyType: this.callerLockupScript.kind === 'P2PKH' ? 'default' : 'bip340-schnorr',
|
|
109
|
+
bytecode: binToHex(bytecode),
|
|
110
|
+
attoAlphAmount: tokens.find((t) => t.id === ALPH_TOKEN_ID)?.amount,
|
|
111
|
+
tokens: tokens.filter((t) => t.id !== ALPH_TOKEN_ID)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private addTokenToMap(tokenId: HexString, amount: bigint, map: Map<HexString, bigint>) {
|
|
116
|
+
const current = map.get(tokenId)
|
|
117
|
+
if (current !== undefined) {
|
|
118
|
+
map.set(tokenId, current + amount)
|
|
119
|
+
} else if (amount > 0n) {
|
|
120
|
+
map.set(tokenId, amount)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private approveTokens(tokens: { id: HexString; amount: bigint }[]): { id: HexString; amount: bigint }[] {
|
|
125
|
+
const tokenAmounts = new Map<HexString, bigint>()
|
|
126
|
+
tokens.forEach((token) => {
|
|
127
|
+
if (!(isHexString(token.id) && token.id.length === 64)) {
|
|
128
|
+
throw new Error(`Invalid token id: ${token.id}`)
|
|
129
|
+
}
|
|
130
|
+
if (token.amount < 0n) {
|
|
131
|
+
throw new Error(`Invalid token amount: ${token.amount}`)
|
|
132
|
+
}
|
|
133
|
+
this.addTokenToMap(token.id, token.amount, tokenAmounts)
|
|
134
|
+
this.addTokenToMap(token.id, token.amount, this.approvedAssets)
|
|
135
|
+
})
|
|
136
|
+
return Array.from(tokenAmounts.entries()).map(([id, amount]) => ({ id, amount }))
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function genApproveAssets(callerLockupScript: LockupScript, tokens: { id: HexString; amount: bigint }[]): Instr[] {
|
|
141
|
+
if (tokens.length === 0) {
|
|
142
|
+
return []
|
|
143
|
+
}
|
|
144
|
+
const approveInstrs = tokens.flatMap((token) => {
|
|
145
|
+
if (token.id === ALPH_TOKEN_ID) {
|
|
146
|
+
return [U256Const(token.amount), ApproveAlph]
|
|
147
|
+
} else {
|
|
148
|
+
const tokenId = BytesConst(hexToBinUnsafe(token.id))
|
|
149
|
+
return [tokenId, U256Const(token.amount), ApproveToken]
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
return [
|
|
153
|
+
AddressConst(callerLockupScript),
|
|
154
|
+
...Array.from(Array(tokens.length - 1).keys()).map(() => Dup),
|
|
155
|
+
...approveInstrs
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function bigintToNumeric(value: bigint): Instr {
|
|
160
|
+
return value >= 0 ? toU256(value) : toI256(value)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function strToNumeric(str: string): Instr {
|
|
164
|
+
const regex = /^-?\d+[ui]?$/
|
|
165
|
+
if (regex.test(str)) {
|
|
166
|
+
if (str.endsWith('i')) return toI256(BigInt(str.slice(0, str.length - 1)))
|
|
167
|
+
if (str.endsWith('u')) return toU256(BigInt(str.slice(0, str.length - 1)))
|
|
168
|
+
return bigintToNumeric(BigInt(str))
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`Invalid number: ${str}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toAddressOpt(str: string): LockupScript | undefined {
|
|
174
|
+
if (!isBase58(str)) return undefined
|
|
175
|
+
try {
|
|
176
|
+
return lockupScriptCodec.decode(base58ToBytes(str))
|
|
177
|
+
} catch (_) {
|
|
178
|
+
return undefined
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function genArgs(args: Val[]): Instr[] {
|
|
183
|
+
return args.flatMap((arg) => {
|
|
184
|
+
if (typeof arg === 'boolean') return arg ? [ConstTrue] : [ConstFalse]
|
|
185
|
+
if (typeof arg === 'bigint') return bigintToNumeric(arg)
|
|
186
|
+
if (typeof arg === 'string') {
|
|
187
|
+
if (isHexString(arg)) return [BytesConst(hexToBinUnsafe(arg))]
|
|
188
|
+
const addressOpt = toAddressOpt(arg)
|
|
189
|
+
if (addressOpt !== undefined) return AddressConst(addressOpt)
|
|
190
|
+
return strToNumeric(arg)
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(arg)) return genArgs(arg)
|
|
193
|
+
if (arg instanceof Map) throw new Error(`Map cannot be used as a function argument`)
|
|
194
|
+
if (typeof arg === 'object') return genArgs(Object.values(arg))
|
|
195
|
+
throw new Error(`Unknown argument type: ${typeof arg}, arg: ${arg}`)
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function genContractCall(contractAddress: string, methodIndex: number, args: Val[], retLength: number): Instr[] {
|
|
200
|
+
const argInstrs = genArgs(args)
|
|
201
|
+
return [
|
|
202
|
+
...argInstrs,
|
|
203
|
+
toU256(BigInt(argInstrs.length)),
|
|
204
|
+
toU256(BigInt(retLength)),
|
|
205
|
+
BytesConst(contractIdFromAddress(contractAddress)),
|
|
206
|
+
CallExternal(methodIndex),
|
|
207
|
+
...Array.from(Array(retLength).keys()).map(() => Pop)
|
|
208
|
+
]
|
|
209
|
+
}
|
package/src/contract/events.ts
CHANGED
|
@@ -19,6 +19,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
19
19
|
import * as web3 from '../global'
|
|
20
20
|
import { node } from '../api'
|
|
21
21
|
import { Subscription, SubscribeOptions } from '../utils'
|
|
22
|
+
import { ContractEvents } from '../api/api-alephium'
|
|
22
23
|
|
|
23
24
|
export interface EventSubscribeOptions<Message> extends SubscribeOptions<Message> {
|
|
24
25
|
onEventCountChanged?: (eventCount: number) => Promise<void> | void
|
|
@@ -40,11 +41,22 @@ export class EventSubscription extends Subscription<node.ContractEvent> {
|
|
|
40
41
|
return this.fromCount
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
private async getEvents(start: number): Promise<ContractEvents> {
|
|
45
|
+
try {
|
|
46
|
+
return await web3
|
|
47
|
+
.getCurrentNodeProvider()
|
|
48
|
+
.events.getEventsContractContractaddress(this.contractAddress, { start })
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof Error && error.message.includes(`Contract events of ${this.contractAddress} not found`)) {
|
|
51
|
+
return { events: [], nextStart: start }
|
|
52
|
+
}
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
override async polling(): Promise<void> {
|
|
44
58
|
try {
|
|
45
|
-
const events = await
|
|
46
|
-
start: this.fromCount
|
|
47
|
-
})
|
|
59
|
+
const events = await this.getEvents(this.fromCount)
|
|
48
60
|
if (this.fromCount === events.nextStart) {
|
|
49
61
|
return
|
|
50
62
|
}
|
package/src/contract/index.ts
CHANGED
package/src/contract/ralph.ts
CHANGED
|
@@ -26,24 +26,11 @@ import {
|
|
|
26
26
|
ConstFalse,
|
|
27
27
|
ConstTrue,
|
|
28
28
|
i256Codec,
|
|
29
|
-
I256Const,
|
|
30
|
-
I256Const0,
|
|
31
|
-
I256Const1,
|
|
32
|
-
I256Const2,
|
|
33
|
-
I256Const3,
|
|
34
|
-
I256Const4,
|
|
35
|
-
I256Const5,
|
|
36
|
-
I256ConstN1,
|
|
37
29
|
i32Codec,
|
|
38
30
|
instrCodec,
|
|
39
31
|
u256Codec,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
U256Const1,
|
|
43
|
-
U256Const2,
|
|
44
|
-
U256Const3,
|
|
45
|
-
U256Const4,
|
|
46
|
-
U256Const5
|
|
32
|
+
toU256,
|
|
33
|
+
toI256
|
|
47
34
|
} from '../codec'
|
|
48
35
|
import { boolCodec } from '../codec/codec'
|
|
49
36
|
import { TraceableError } from '../error'
|
|
@@ -118,43 +105,11 @@ function invalidScriptField(tpe: string, value: Val): Error {
|
|
|
118
105
|
}
|
|
119
106
|
|
|
120
107
|
function encodeScriptFieldI256(value: bigint): Uint8Array {
|
|
121
|
-
|
|
122
|
-
case 0n:
|
|
123
|
-
return instrCodec.encode(I256Const0)
|
|
124
|
-
case 1n:
|
|
125
|
-
return instrCodec.encode(I256Const1)
|
|
126
|
-
case 2n:
|
|
127
|
-
return instrCodec.encode(I256Const2)
|
|
128
|
-
case 3n:
|
|
129
|
-
return instrCodec.encode(I256Const3)
|
|
130
|
-
case 4n:
|
|
131
|
-
return instrCodec.encode(I256Const4)
|
|
132
|
-
case 5n:
|
|
133
|
-
return instrCodec.encode(I256Const5)
|
|
134
|
-
case -1n:
|
|
135
|
-
return instrCodec.encode(I256ConstN1)
|
|
136
|
-
default:
|
|
137
|
-
return instrCodec.encode(I256Const(value))
|
|
138
|
-
}
|
|
108
|
+
return instrCodec.encode(toI256(value))
|
|
139
109
|
}
|
|
140
110
|
|
|
141
111
|
function encodeScriptFieldU256(value: bigint): Uint8Array {
|
|
142
|
-
|
|
143
|
-
case 0n:
|
|
144
|
-
return instrCodec.encode(U256Const0)
|
|
145
|
-
case 1n:
|
|
146
|
-
return instrCodec.encode(U256Const1)
|
|
147
|
-
case 2n:
|
|
148
|
-
return instrCodec.encode(U256Const2)
|
|
149
|
-
case 3n:
|
|
150
|
-
return instrCodec.encode(U256Const3)
|
|
151
|
-
case 4n:
|
|
152
|
-
return instrCodec.encode(U256Const4)
|
|
153
|
-
case 5n:
|
|
154
|
-
return instrCodec.encode(U256Const5)
|
|
155
|
-
default:
|
|
156
|
-
return instrCodec.encode(U256Const(value))
|
|
157
|
-
}
|
|
112
|
+
return instrCodec.encode(toU256(value))
|
|
158
113
|
}
|
|
159
114
|
|
|
160
115
|
export function encodeScriptFieldAsString(tpe: string, value: Val): string {
|
package/src/exchange/exchange.ts
CHANGED
|
@@ -17,10 +17,10 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { AddressType, addressFromPublicKey, addressFromScript } from '../address'
|
|
20
|
-
import { base58ToBytes, binToHex, hexToBinUnsafe, isHexString } from '../utils'
|
|
20
|
+
import { base58ToBytes, binToHex, HexString, hexToBinUnsafe, isHexString } from '../utils'
|
|
21
21
|
import { Transaction } from '../api/api-alephium'
|
|
22
22
|
import { Address } from '../signer'
|
|
23
|
-
import { P2SH, unlockScriptCodec } from '../codec/unlock-script-codec'
|
|
23
|
+
import { encodedSameAsPrevious, P2SH, unlockScriptCodec } from '../codec/unlock-script-codec'
|
|
24
24
|
import { scriptCodec } from '../codec/script-codec'
|
|
25
25
|
import { TraceableError } from '../error'
|
|
26
26
|
|
|
@@ -40,19 +40,15 @@ export function isALPHTransferTx(tx: Transaction): boolean {
|
|
|
40
40
|
return isTransferTx(tx) && checkALPHOutput(tx)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
inputAddresses.push(address)
|
|
53
|
-
}
|
|
54
|
-
} catch (_) {}
|
|
55
|
-
}
|
|
43
|
+
export interface BaseDepositInfo {
|
|
44
|
+
targetAddress: Address
|
|
45
|
+
depositAmount: bigint
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getALPHDepositInfo(tx: Transaction): BaseDepositInfo[] {
|
|
49
|
+
if (!isALPHTransferTx(tx)) return []
|
|
50
|
+
|
|
51
|
+
const inputAddresses = getInputAddresses(tx)
|
|
56
52
|
const result = new Map<Address, bigint>()
|
|
57
53
|
tx.unsigned.fixedOutputs.forEach((o) => {
|
|
58
54
|
if (!inputAddresses.includes(o.address)) {
|
|
@@ -67,7 +63,63 @@ export function getALPHDepositInfo(tx: Transaction): { targetAddress: Address; d
|
|
|
67
63
|
return Array.from(result.entries()).map(([key, value]) => ({ targetAddress: key, depositAmount: value }))
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
function getInputAddresses(tx: Transaction): Address[] {
|
|
67
|
+
const inputAddresses: Address[] = []
|
|
68
|
+
for (const input of tx.unsigned.inputs) {
|
|
69
|
+
try {
|
|
70
|
+
if (input.unlockScript === binToHex(encodedSameAsPrevious)) continue
|
|
71
|
+
const address = getAddressFromUnlockScript(input.unlockScript)
|
|
72
|
+
if (!inputAddresses.includes(address)) {
|
|
73
|
+
inputAddresses.push(address)
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new TraceableError(`Failed to decode address from unlock script`, error)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return inputAddresses
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TokenDepositInfo extends BaseDepositInfo {
|
|
83
|
+
tokenId: HexString
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DepositInfo {
|
|
87
|
+
alph: BaseDepositInfo[]
|
|
88
|
+
tokens: TokenDepositInfo[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getDepositInfo(tx: Transaction): DepositInfo {
|
|
92
|
+
if (!isTransferTx(tx)) return { alph: [], tokens: [] }
|
|
93
|
+
|
|
94
|
+
const inputAddresses = getInputAddresses(tx)
|
|
95
|
+
const alphDepositInfos = new Map<Address, bigint>()
|
|
96
|
+
const tokenDepositInfos = new Map<HexString, Map<Address, bigint>>()
|
|
97
|
+
tx.unsigned.fixedOutputs.forEach((o) => {
|
|
98
|
+
if (!inputAddresses.includes(o.address)) {
|
|
99
|
+
const alphAmount = alphDepositInfos.get(o.address) ?? 0n
|
|
100
|
+
alphDepositInfos.set(o.address, alphAmount + BigInt(o.attoAlphAmount))
|
|
101
|
+
|
|
102
|
+
o.tokens.forEach((token) => {
|
|
103
|
+
const depositPerToken = tokenDepositInfos.get(token.id) ?? new Map<Address, bigint>()
|
|
104
|
+
const currentAmount = depositPerToken.get(o.address) ?? 0n
|
|
105
|
+
depositPerToken.set(o.address, currentAmount + BigInt(token.amount))
|
|
106
|
+
tokenDepositInfos.set(token.id, depositPerToken)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
return {
|
|
111
|
+
alph: Array.from(alphDepositInfos.entries()).map(([key, value]) => ({ targetAddress: key, depositAmount: value })),
|
|
112
|
+
tokens: Array.from(tokenDepositInfos.entries()).flatMap(([tokenId, depositPerToken]) => {
|
|
113
|
+
return Array.from(depositPerToken.entries()).map(([targetAddress, depositAmount]) => ({
|
|
114
|
+
tokenId,
|
|
115
|
+
targetAddress,
|
|
116
|
+
depositAmount
|
|
117
|
+
}))
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// we assume that the tx is a simple transfer tx, i.e. isALPHTransferTx(tx) || isTokenTransferTx(tx)
|
|
71
123
|
export function getSenderAddress(tx: Transaction): Address {
|
|
72
124
|
return getAddressFromUnlockScript(tx.unsigned.inputs[0].unlockScript)
|
|
73
125
|
}
|
|
@@ -115,7 +167,7 @@ function checkALPHOutput(tx: Transaction): boolean {
|
|
|
115
167
|
return outputs.every((o) => o.tokens.length === 0)
|
|
116
168
|
}
|
|
117
169
|
|
|
118
|
-
function isTransferTx(tx: Transaction): boolean {
|
|
170
|
+
export function isTransferTx(tx: Transaction): boolean {
|
|
119
171
|
if (
|
|
120
172
|
tx.contractInputs.length !== 0 ||
|
|
121
173
|
tx.generatedOutputs.length !== 0 ||
|
package/src/exchange/index.ts
CHANGED
|
@@ -16,4 +16,13 @@ You should have received a copy of the GNU Lesser General Public License
|
|
|
16
16
|
along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
export {
|
|
19
|
+
export {
|
|
20
|
+
validateExchangeAddress,
|
|
21
|
+
getSenderAddress,
|
|
22
|
+
isALPHTransferTx,
|
|
23
|
+
getALPHDepositInfo,
|
|
24
|
+
BaseDepositInfo,
|
|
25
|
+
TokenDepositInfo,
|
|
26
|
+
DepositInfo,
|
|
27
|
+
getDepositInfo
|
|
28
|
+
} from './exchange'
|
package/src/signer/tx-builder.ts
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
import { unsignedTxCodec } from '../codec'
|
|
40
40
|
import { groupIndexOfTransaction } from '../transaction'
|
|
41
41
|
import { blakeHash } from '../codec/hash'
|
|
42
|
-
import { BuildDeployContractTxResult, BuildChainedTx } from '../api/api-alephium'
|
|
42
|
+
import { BuildDeployContractTxResult, BuildChainedTx, BuildExecuteScriptTxResult } from '../api/api-alephium'
|
|
43
43
|
|
|
44
44
|
export abstract class TransactionBuilder {
|
|
45
45
|
abstract get nodeProvider(): NodeProvider
|
|
@@ -140,7 +140,7 @@ export abstract class TransactionBuilder {
|
|
|
140
140
|
} as SignDeployContractChainedTxResult
|
|
141
141
|
}
|
|
142
142
|
case 'ExecuteScript': {
|
|
143
|
-
const buildExecuteScriptTxResult = buildResult.value
|
|
143
|
+
const buildExecuteScriptTxResult = buildResult.value as BuildExecuteScriptTxResult
|
|
144
144
|
return {
|
|
145
145
|
...this.convertExecuteScriptTxResult(buildExecuteScriptTxResult),
|
|
146
146
|
type: buildResultType
|