@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.
Files changed (41) hide show
  1. package/dist/alephium-web3.min.js +1 -1
  2. package/dist/alephium-web3.min.js.map +1 -1
  3. package/dist/src/api/api-alephium.d.ts +25 -6
  4. package/dist/src/api/api-alephium.js +33 -4
  5. package/dist/src/api/api-explorer.d.ts +810 -819
  6. package/dist/src/api/api-explorer.js +350 -360
  7. package/dist/src/codec/instr-codec.d.ts +2 -0
  8. package/dist/src/codec/instr-codec.js +54 -1
  9. package/dist/src/codec/unlock-script-codec.d.ts +1 -0
  10. package/dist/src/codec/unlock-script-codec.js +2 -1
  11. package/dist/src/constants.d.ts +4 -0
  12. package/dist/src/constants.js +5 -1
  13. package/dist/src/contract/contract.d.ts +5 -1
  14. package/dist/src/contract/contract.js +44 -8
  15. package/dist/src/contract/dapp-tx-builder.d.ts +26 -0
  16. package/dist/src/contract/dapp-tx-builder.js +187 -0
  17. package/dist/src/contract/events.d.ts +1 -0
  18. package/dist/src/contract/events.js +14 -3
  19. package/dist/src/contract/index.d.ts +1 -0
  20. package/dist/src/contract/index.js +3 -0
  21. package/dist/src/contract/ralph.js +2 -34
  22. package/dist/src/exchange/exchange.d.ts +13 -2
  23. package/dist/src/exchange/exchange.js +52 -14
  24. package/dist/src/exchange/index.d.ts +1 -1
  25. package/dist/src/exchange/index.js +3 -2
  26. package/dist/src/signer/types.d.ts +1 -0
  27. package/package.json +5 -5
  28. package/src/api/api-alephium.ts +49 -9
  29. package/src/api/api-explorer.ts +990 -1000
  30. package/src/codec/instr-codec.ts +56 -1
  31. package/src/codec/unlock-script-codec.ts +2 -0
  32. package/src/constants.ts +4 -0
  33. package/src/contract/contract.ts +46 -7
  34. package/src/contract/dapp-tx-builder.ts +209 -0
  35. package/src/contract/events.ts +15 -3
  36. package/src/contract/index.ts +1 -0
  37. package/src/contract/ralph.ts +4 -49
  38. package/src/exchange/exchange.ts +69 -17
  39. package/src/exchange/index.ts +10 -1
  40. package/src/signer/tx-builder.ts +2 -2
  41. package/src/signer/types.ts +1 -0
@@ -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
+ }
@@ -64,3 +64,5 @@ export const unlockScriptCodec = new EnumCodec<UnlockScript>('unlock script', {
64
64
  P2SH: p2shCodec,
65
65
  SameAsPrevious: sameAsPreviousCodec
66
66
  })
67
+
68
+ export const encodedSameAsPrevious = unlockScriptCodec.encode({ kind: 'SameAsPrevious', value: 'SameAsPrevious' })
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
@@ -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
- if (this.publicFunctions().length == this.functions.length) {
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: this.getMethodIndex(funcName),
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 methodUsePreapprovedAssets = contract.contract.isMethodUsePreapprovedAssets(methodIndex)
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() && (await contract.contract.isDevnet(signer))) {
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
+ }
@@ -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 web3.getCurrentNodeProvider().events.getEventsContractContractaddress(this.contractAddress, {
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
  }
@@ -21,3 +21,4 @@ export * from './contract'
21
21
  export * from './events'
22
22
  export * from './script-simulator'
23
23
  export * from './deployment'
24
+ export { DappTransactionBuilder } from './dapp-tx-builder'
@@ -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
- U256Const,
41
- U256Const0,
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
- switch (value) {
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
- switch (value) {
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 {
@@ -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 function getALPHDepositInfo(tx: Transaction): { targetAddress: Address; depositAmount: bigint }[] {
44
- if (!isALPHTransferTx(tx)) {
45
- return []
46
- }
47
- const inputAddresses: Address[] = []
48
- for (const input of tx.unsigned.inputs) {
49
- try {
50
- const address = getAddressFromUnlockScript(input.unlockScript)
51
- if (!inputAddresses.includes(address)) {
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
- // we assume that the tx is a simple transfer tx, i.e. isSimpleALPHTransferTx(tx) == true
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 ||
@@ -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 { validateExchangeAddress, isALPHTransferTx, getSenderAddress, getALPHDepositInfo } from './exchange'
19
+ export {
20
+ validateExchangeAddress,
21
+ getSenderAddress,
22
+ isALPHTransferTx,
23
+ getALPHDepositInfo,
24
+ BaseDepositInfo,
25
+ TokenDepositInfo,
26
+ DepositInfo,
27
+ getDepositInfo
28
+ } from './exchange'
@@ -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
@@ -113,6 +113,7 @@ export interface SignExecuteScriptTxResult {
113
113
  signature: string
114
114
  gasAmount: number
115
115
  gasPrice: Number256
116
+ simulatedOutputs: node.Output[]
116
117
  }
117
118
  assertType<
118
119
  Eq<