@alephium/ledger-app 0.2.1 → 0.4.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/src/index.d.ts +4 -2
- package/dist/src/index.js +35 -10
- package/dist/test/utils.d.ts +16 -0
- package/dist/test/utils.js +215 -0
- package/dist/test/wallet.test.js +383 -0
- package/docker/devnet.conf +55 -0
- package/docker/docker-compose.yaml +22 -0
- package/package.json +11 -11
- package/src/index.ts +40 -11
- package/test/utils.ts +222 -0
- package/test/wallet.test.ts +412 -0
- package/dist/test/release.test.js +0 -26
- package/dist/test/speculos.test.d.ts +0 -1
- package/dist/test/speculos.test.js +0 -123
- package/test/release.test.ts +0 -28
- package/test/speculos.test.ts +0 -111
- /package/dist/test/{release.test.d.ts → wallet.test.d.ts} +0 -0
package/test/utils.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import SpeculosTransport from '@ledgerhq/hw-transport-node-speculos'
|
|
2
|
+
import fetch from 'node-fetch'
|
|
3
|
+
import { sleep } from '@alephium/web3'
|
|
4
|
+
import Transport from '@ledgerhq/hw-transport'
|
|
5
|
+
import NodeTransport from '@ledgerhq/hw-transport-node-hid'
|
|
6
|
+
|
|
7
|
+
async function pressButton(button: 'left' | 'right' | 'both') {
|
|
8
|
+
await sleep(1000)
|
|
9
|
+
return fetch(`http://localhost:25000/button/${button}`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
body: JSON.stringify({ action: 'press-and-release' })
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function clickAndApprove(times: number) {
|
|
16
|
+
for (let i = 0; i < times; i++) {
|
|
17
|
+
await pressButton('right')
|
|
18
|
+
}
|
|
19
|
+
await pressButton('both')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export enum OutputType {
|
|
23
|
+
Base,
|
|
24
|
+
Multisig,
|
|
25
|
+
Token,
|
|
26
|
+
MultisigAndToken
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NanosClickTable = new Map([
|
|
30
|
+
[OutputType.Base, 5],
|
|
31
|
+
[OutputType.Multisig, 10],
|
|
32
|
+
[OutputType.Token, 11],
|
|
33
|
+
[OutputType.MultisigAndToken, 16],
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
const NanospClickTable = new Map([
|
|
37
|
+
[OutputType.Base, 3],
|
|
38
|
+
[OutputType.Multisig, 5],
|
|
39
|
+
[OutputType.Token, 6],
|
|
40
|
+
[OutputType.MultisigAndToken, 8],
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
const StaxClickTable = new Map([
|
|
44
|
+
[OutputType.Base, 2],
|
|
45
|
+
[OutputType.Multisig, 3],
|
|
46
|
+
[OutputType.Token, 3],
|
|
47
|
+
[OutputType.MultisigAndToken, 4],
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
function getOutputClickSize(outputType: OutputType) {
|
|
51
|
+
const model = process.env.MODEL
|
|
52
|
+
switch (model) {
|
|
53
|
+
case 'nanos': return NanosClickTable.get(outputType)!
|
|
54
|
+
case 'nanosp':
|
|
55
|
+
case 'nanox': return NanospClickTable.get(outputType)!
|
|
56
|
+
case 'stax':
|
|
57
|
+
case 'flex': return StaxClickTable.get(outputType)!
|
|
58
|
+
default: throw new Error(`Unknown model ${model}`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function click(outputs: OutputType[], hasExternalInputs: boolean) {
|
|
63
|
+
await sleep(1000);
|
|
64
|
+
if (hasExternalInputs) {
|
|
65
|
+
await clickAndApprove(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
69
|
+
await clickAndApprove(getOutputClickSize(outputs[index]))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await clickAndApprove(1) // fees
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface Position {
|
|
76
|
+
x: number
|
|
77
|
+
y: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const STAX_CONTINUE_POSITION = { x: 342, y: 606 }
|
|
81
|
+
const STAX_APPROVE_POSITION = { x: 200, y: 515 }
|
|
82
|
+
const STAX_REJECT_POSITION = { x: 36, y: 606 }
|
|
83
|
+
const STAX_SETTINGS_POSITION = { x: 342, y: 55 }
|
|
84
|
+
const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 }
|
|
85
|
+
|
|
86
|
+
const FLEX_CONTINUE_POSITION = { x: 430, y: 550 }
|
|
87
|
+
const FLEX_APPROVE_POSITION = { x: 240, y: 435 }
|
|
88
|
+
const FLEX_REJECT_POSITION = { x: 55, y: 530 }
|
|
89
|
+
const FLEX_SETTINGS_POSITION = { x: 405, y: 75 }
|
|
90
|
+
const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 }
|
|
91
|
+
|
|
92
|
+
async function touchPosition(pos: Position) {
|
|
93
|
+
await sleep(1000)
|
|
94
|
+
return fetch(`http://localhost:25000/finger`, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y })
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function _touch(times: number) {
|
|
101
|
+
let continuePos = process.env.MODEL === 'stax' ? STAX_CONTINUE_POSITION : FLEX_CONTINUE_POSITION
|
|
102
|
+
for (let i = 0; i < times; i += 1) {
|
|
103
|
+
await touchPosition(continuePos)
|
|
104
|
+
}
|
|
105
|
+
let approvePos = process.env.MODEL === 'stax' ? STAX_APPROVE_POSITION : FLEX_APPROVE_POSITION
|
|
106
|
+
await touchPosition(approvePos)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function staxFlexApproveOnce() {
|
|
110
|
+
if (process.env.MODEL === 'stax') {
|
|
111
|
+
await touchPosition(STAX_APPROVE_POSITION)
|
|
112
|
+
} else {
|
|
113
|
+
await touchPosition(FLEX_APPROVE_POSITION)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function touch(outputs: OutputType[], hasExternalInputs: boolean) {
|
|
118
|
+
await sleep(1000);
|
|
119
|
+
if (hasExternalInputs) {
|
|
120
|
+
await staxFlexApproveOnce()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
124
|
+
await _touch(getOutputClickSize(outputs[index]))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await _touch(2) // fees
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function approveTx(outputs: OutputType[], hasExternalInputs: boolean = false) {
|
|
131
|
+
if (!needToAutoApprove()) return
|
|
132
|
+
const isSelfTransfer = outputs.length === 0 && !hasExternalInputs
|
|
133
|
+
if (isSelfTransfer) {
|
|
134
|
+
if (isStaxOrFlex()) {
|
|
135
|
+
await _touch(2)
|
|
136
|
+
} else {
|
|
137
|
+
await clickAndApprove(2)
|
|
138
|
+
}
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (isStaxOrFlex()) {
|
|
143
|
+
await touch(outputs, hasExternalInputs)
|
|
144
|
+
} else {
|
|
145
|
+
await click(outputs, hasExternalInputs)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function approveHash() {
|
|
150
|
+
if (!needToAutoApprove()) return
|
|
151
|
+
if (isStaxOrFlex()) {
|
|
152
|
+
return await _touch(3)
|
|
153
|
+
}
|
|
154
|
+
if (process.env.MODEL === 'nanos') {
|
|
155
|
+
await clickAndApprove(5)
|
|
156
|
+
} else {
|
|
157
|
+
await clickAndApprove(3)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function approveAddress() {
|
|
162
|
+
if (!needToAutoApprove()) return
|
|
163
|
+
if (isStaxOrFlex()) {
|
|
164
|
+
return await _touch(2)
|
|
165
|
+
}
|
|
166
|
+
if (process.env.MODEL === 'nanos') {
|
|
167
|
+
await clickAndApprove(4)
|
|
168
|
+
} else {
|
|
169
|
+
await clickAndApprove(2)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isStaxOrFlex(): boolean {
|
|
174
|
+
return !process.env.MODEL!.startsWith('nano')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function skipBlindSigningWarning() {
|
|
178
|
+
if (!needToAutoApprove()) return
|
|
179
|
+
if (isStaxOrFlex()) {
|
|
180
|
+
const rejectPos = process.env.MODEL === 'stax' ? STAX_REJECT_POSITION : FLEX_REJECT_POSITION
|
|
181
|
+
touchPosition(rejectPos)
|
|
182
|
+
} else {
|
|
183
|
+
clickAndApprove(3)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function enableBlindSigning() {
|
|
188
|
+
if (!needToAutoApprove()) return
|
|
189
|
+
if (isStaxOrFlex()) {
|
|
190
|
+
const settingsPos = process.env.MODEL === 'stax' ? STAX_SETTINGS_POSITION : FLEX_SETTINGS_POSITION
|
|
191
|
+
const blindSettingPos = process.env.MODEL === 'stax' ? STAX_BLIND_SETTING_POSITION : FLEX_BLIND_SETTING_POSITION
|
|
192
|
+
await touchPosition(settingsPos)
|
|
193
|
+
await touchPosition(blindSettingPos)
|
|
194
|
+
await touchPosition(settingsPos)
|
|
195
|
+
} else {
|
|
196
|
+
await clickAndApprove(2)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getRandomInt(min: number, max: number) {
|
|
201
|
+
min = Math.ceil(min)
|
|
202
|
+
max = Math.floor(max)
|
|
203
|
+
return Math.floor(Math.random() * (max - min) + min) // The maximum is exclusive and the minimum is inclusive
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function needToAutoApprove(): boolean {
|
|
207
|
+
switch (process.env.BACKEND) {
|
|
208
|
+
case "speculos": return true
|
|
209
|
+
case "device": return false
|
|
210
|
+
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const ApduPort = 9999
|
|
215
|
+
|
|
216
|
+
export async function createTransport(): Promise<Transport> {
|
|
217
|
+
switch (process.env.BACKEND) {
|
|
218
|
+
case "speculos": return SpeculosTransport.open({ apduPort: ApduPort })
|
|
219
|
+
case "device": return NodeTransport.open('')
|
|
220
|
+
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import SpeculosTransport from '@ledgerhq/hw-transport-node-speculos'
|
|
2
|
+
import AlephiumApp, { GROUP_NUM } from '../src'
|
|
3
|
+
import { ALPH_TOKEN_ID, Address, NodeProvider, ONE_ALPH, binToHex, codec, groupOfAddress, node, sleep, transactionVerifySignature, waitForTxConfirmation, web3 } from '@alephium/web3'
|
|
4
|
+
import { getSigner, mintToken, transfer } from '@alephium/web3-test'
|
|
5
|
+
import { PrivateKeyWallet } from '@alephium/web3-wallet'
|
|
6
|
+
import blake from 'blakejs'
|
|
7
|
+
import { approveAddress, approveHash, approveTx, createTransport, enableBlindSigning, getRandomInt, needToAutoApprove, OutputType, skipBlindSigningWarning, staxFlexApproveOnce } from './utils'
|
|
8
|
+
|
|
9
|
+
describe('ledger wallet', () => {
|
|
10
|
+
const nodeProvider = new NodeProvider("http://127.0.0.1:22973")
|
|
11
|
+
web3.setCurrentNodeProvider(nodeProvider)
|
|
12
|
+
let pathIndex: number
|
|
13
|
+
let path: string
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
pathIndex = getRandomInt(0, 1000000)
|
|
17
|
+
path = `m/44'/1234'/0'/0/` + pathIndex
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
async function transferToAddress(address: Address, amount: bigint = ONE_ALPH * 10n) {
|
|
21
|
+
const balance0 = await getALPHBalance(address)
|
|
22
|
+
const fromAccount = await getSigner()
|
|
23
|
+
const transferResult = await transfer(fromAccount, address, ALPH_TOKEN_ID, amount)
|
|
24
|
+
await waitForTxConfirmation(transferResult.txId, 1, 1000)
|
|
25
|
+
const balance1 = await getALPHBalance(address)
|
|
26
|
+
expect(balance1 - balance0).toEqual(amount)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getALPHBalance(address: Address) {
|
|
30
|
+
const balances = await nodeProvider.addresses.getAddressesAddressBalance(address)
|
|
31
|
+
return BigInt(balances.balance)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
it('should get version', async () => {
|
|
35
|
+
const transport = await createTransport()
|
|
36
|
+
const app = new AlephiumApp(transport)
|
|
37
|
+
const version = await app.getVersion()
|
|
38
|
+
expect(version).toBe('0.4.0')
|
|
39
|
+
await app.close()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should get public key', async () => {
|
|
43
|
+
const transport = await createTransport()
|
|
44
|
+
const app = new AlephiumApp(transport)
|
|
45
|
+
const [account, hdIndex] = await app.getAccount(path)
|
|
46
|
+
expect(hdIndex).toBe(pathIndex)
|
|
47
|
+
console.log(account)
|
|
48
|
+
await app.close()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should get public key and confirm address', async () => {
|
|
52
|
+
const transport = await createTransport()
|
|
53
|
+
const app = new AlephiumApp(transport)
|
|
54
|
+
approveAddress()
|
|
55
|
+
const [account, hdIndex] = await app.getAccount(path, undefined, undefined, true)
|
|
56
|
+
expect(hdIndex).toBe(pathIndex)
|
|
57
|
+
console.log(account)
|
|
58
|
+
await app.close()
|
|
59
|
+
}, 30000)
|
|
60
|
+
|
|
61
|
+
it('should get public key for group', async () => {
|
|
62
|
+
const transport = await createTransport()
|
|
63
|
+
const app = new AlephiumApp(transport)
|
|
64
|
+
for (let group = 0; group < GROUP_NUM; group++) {
|
|
65
|
+
const [account, hdIndex] = await app.getAccount(path, group)
|
|
66
|
+
expect(hdIndex >= pathIndex).toBe(true)
|
|
67
|
+
expect(groupOfAddress(account.address)).toBe(group)
|
|
68
|
+
expect(account.keyType).toBe('default')
|
|
69
|
+
}
|
|
70
|
+
await app.close()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should get public key for group for Schnorr signature', async () => {
|
|
74
|
+
const transport = await createTransport()
|
|
75
|
+
const app = new AlephiumApp(transport)
|
|
76
|
+
for (let group = 0; group < GROUP_NUM; group++) {
|
|
77
|
+
await expect(app.getAccount(path, group, 'bip340-schnorr')).rejects.toThrow('BIP340-Schnorr is not supported yet')
|
|
78
|
+
}
|
|
79
|
+
await app.close()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should sign hash', async () => {
|
|
83
|
+
const transport = await createTransport()
|
|
84
|
+
const app = new AlephiumApp(transport)
|
|
85
|
+
|
|
86
|
+
const [account] = await app.getAccount(path)
|
|
87
|
+
console.log(account)
|
|
88
|
+
|
|
89
|
+
const hash = Buffer.from(blake.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32))
|
|
90
|
+
approveHash()
|
|
91
|
+
const signature = await app.signHash(path, hash)
|
|
92
|
+
console.log(signature)
|
|
93
|
+
await app.close()
|
|
94
|
+
|
|
95
|
+
expect(transactionVerifySignature(hash.toString('hex'), account.publicKey, signature)).toBe(true)
|
|
96
|
+
}, 10000)
|
|
97
|
+
|
|
98
|
+
it('shoudl transfer alph to one address', async () => {
|
|
99
|
+
const transport = await createTransport()
|
|
100
|
+
const app = new AlephiumApp(transport)
|
|
101
|
+
const [testAccount] = await app.getAccount(path)
|
|
102
|
+
await transferToAddress(testAccount.address)
|
|
103
|
+
|
|
104
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
105
|
+
fromPublicKey: testAccount.publicKey,
|
|
106
|
+
destinations: [
|
|
107
|
+
{
|
|
108
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
109
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
approveTx([OutputType.Base])
|
|
115
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
116
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
117
|
+
|
|
118
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
119
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
120
|
+
signature: signature
|
|
121
|
+
})
|
|
122
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
123
|
+
const balance = await getALPHBalance(testAccount.address)
|
|
124
|
+
expect(balance < (ONE_ALPH * 8n)).toEqual(true)
|
|
125
|
+
|
|
126
|
+
await app.close()
|
|
127
|
+
}, 120000)
|
|
128
|
+
|
|
129
|
+
it('should transfer alph to multiple addresses', async () => {
|
|
130
|
+
const transport = await createTransport()
|
|
131
|
+
const app = new AlephiumApp(transport)
|
|
132
|
+
const [testAccount] = await app.getAccount(path)
|
|
133
|
+
await transferToAddress(testAccount.address)
|
|
134
|
+
|
|
135
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
136
|
+
fromPublicKey: testAccount.publicKey,
|
|
137
|
+
destinations: [
|
|
138
|
+
{
|
|
139
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
140
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
address: '1F1fu6GjuN9yUVRFVcgQKWwiTg8RMzKFv1BZFDwFcfWJq',
|
|
144
|
+
attoAlphAmount: (ONE_ALPH * 3n).toString(),
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
approveTx(Array(2).fill(OutputType.Base))
|
|
150
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
151
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
152
|
+
|
|
153
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
154
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
155
|
+
signature: signature
|
|
156
|
+
})
|
|
157
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
158
|
+
const balance1 = await getALPHBalance(testAccount.address)
|
|
159
|
+
expect(balance1 < (ONE_ALPH * 5n)).toEqual(true)
|
|
160
|
+
|
|
161
|
+
await app.close()
|
|
162
|
+
}, 120000)
|
|
163
|
+
|
|
164
|
+
it('should transfer alph to multisig address', async () => {
|
|
165
|
+
const transport = await createTransport()
|
|
166
|
+
const app = new AlephiumApp(transport)
|
|
167
|
+
const [testAccount] = await app.getAccount(path)
|
|
168
|
+
await transferToAddress(testAccount.address)
|
|
169
|
+
|
|
170
|
+
const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
|
|
171
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
172
|
+
fromPublicKey: testAccount.publicKey,
|
|
173
|
+
destinations: [
|
|
174
|
+
{
|
|
175
|
+
address: multiSigAddress,
|
|
176
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
approveTx([OutputType.Multisig]);
|
|
182
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
183
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
184
|
+
|
|
185
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
186
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
187
|
+
signature: signature
|
|
188
|
+
})
|
|
189
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
190
|
+
const balance1 = await getALPHBalance(testAccount.address)
|
|
191
|
+
expect(balance1 < (ONE_ALPH * 8n)).toEqual(true)
|
|
192
|
+
|
|
193
|
+
await app.close()
|
|
194
|
+
}, 120000)
|
|
195
|
+
|
|
196
|
+
it('should transfer token to multisig address', async () => {
|
|
197
|
+
const transport = await createTransport()
|
|
198
|
+
const app = new AlephiumApp(transport)
|
|
199
|
+
const [testAccount] = await app.getAccount(path)
|
|
200
|
+
await transferToAddress(testAccount.address)
|
|
201
|
+
|
|
202
|
+
const tokenInfo = await mintToken(testAccount.address, 2222222222222222222222222n);
|
|
203
|
+
|
|
204
|
+
const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
|
|
205
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
206
|
+
fromPublicKey: testAccount.publicKey,
|
|
207
|
+
destinations: [
|
|
208
|
+
{
|
|
209
|
+
address: multiSigAddress,
|
|
210
|
+
attoAlphAmount: (ONE_ALPH * 5n).toString(),
|
|
211
|
+
tokens: [
|
|
212
|
+
{
|
|
213
|
+
id: tokenInfo.contractId,
|
|
214
|
+
amount: "1111111111111111111111111",
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
approveTx([OutputType.MultisigAndToken, OutputType.Multisig])
|
|
222
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
223
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
224
|
+
|
|
225
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
226
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
227
|
+
signature: signature
|
|
228
|
+
})
|
|
229
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
230
|
+
const balances = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
|
|
231
|
+
const alphBalance = BigInt(balances.balance)
|
|
232
|
+
expect(alphBalance < (ONE_ALPH * 5n)).toEqual(true)
|
|
233
|
+
|
|
234
|
+
expect(balances.tokenBalances!.length).toEqual(1)
|
|
235
|
+
const token = balances.tokenBalances![0]
|
|
236
|
+
expect(token.id).toEqual(tokenInfo.contractId)
|
|
237
|
+
expect(token.amount).toEqual('1111111111111111111111111')
|
|
238
|
+
|
|
239
|
+
await app.close()
|
|
240
|
+
}, 120000)
|
|
241
|
+
|
|
242
|
+
it('should transfer from multiple inputs', async () => {
|
|
243
|
+
const transport = await createTransport()
|
|
244
|
+
const app = new AlephiumApp(transport)
|
|
245
|
+
const [testAccount] = await app.getAccount(path)
|
|
246
|
+
for (let i = 0; i < 20; i += 1) {
|
|
247
|
+
await transferToAddress(testAccount.address, ONE_ALPH)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
251
|
+
fromPublicKey: testAccount.publicKey,
|
|
252
|
+
destinations: [
|
|
253
|
+
{
|
|
254
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
255
|
+
attoAlphAmount: (ONE_ALPH * 19n).toString(),
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
approveTx([OutputType.Base])
|
|
261
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
262
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
263
|
+
|
|
264
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
265
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
266
|
+
signature: signature
|
|
267
|
+
})
|
|
268
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
269
|
+
const balance = await getALPHBalance(testAccount.address)
|
|
270
|
+
expect(balance < (ONE_ALPH * 1n)).toEqual(true)
|
|
271
|
+
|
|
272
|
+
await app.close()
|
|
273
|
+
}, 120000)
|
|
274
|
+
|
|
275
|
+
function getAccount(groupIndex: number): { account: PrivateKeyWallet, unlockScript: string } {
|
|
276
|
+
const useDefaultKeyType = Math.random() >= 0.5
|
|
277
|
+
if (useDefaultKeyType) {
|
|
278
|
+
const account = PrivateKeyWallet.Random(groupIndex)
|
|
279
|
+
return { account, unlockScript: '00' + account.publicKey }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const account = PrivateKeyWallet.Random(groupIndex, nodeProvider, 'bip340-schnorr')
|
|
283
|
+
const unlockScript = '02' + `0101000000000458144020${account.publicKey}8685` + '00'
|
|
284
|
+
return { account, unlockScript }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
it('should test external inputs', async () => {
|
|
288
|
+
const transport = await createTransport()
|
|
289
|
+
const app = new AlephiumApp(transport)
|
|
290
|
+
const [testAccount] = await app.getAccount(path)
|
|
291
|
+
const { account: newAccount, unlockScript: unlockScript0 } = getAccount(testAccount.group)
|
|
292
|
+
for (let i = 0; i < 2; i += 1) {
|
|
293
|
+
await transferToAddress(testAccount.address, ONE_ALPH)
|
|
294
|
+
await transferToAddress(newAccount.address, ONE_ALPH)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const utxos0 = await nodeProvider.addresses.getAddressesAddressUtxos(newAccount.address)
|
|
298
|
+
expect(utxos0.utxos.length).toEqual(2)
|
|
299
|
+
const utxos1 = await nodeProvider.addresses.getAddressesAddressUtxos(testAccount.address)
|
|
300
|
+
expect(utxos1.utxos.length).toEqual(2)
|
|
301
|
+
|
|
302
|
+
const useSameAsPrevious = Math.random() >= 0.5
|
|
303
|
+
const inputs0: node.AssetInput[] = utxos0.utxos.map((utxo, index) => {
|
|
304
|
+
const unlockScript = index > 0 && useSameAsPrevious ? '03' : unlockScript0
|
|
305
|
+
return { outputRef: utxo.ref, unlockScript }
|
|
306
|
+
})
|
|
307
|
+
const unlockScript1 = '00' + testAccount.publicKey
|
|
308
|
+
const inputs1: node.AssetInput[] = utxos1.utxos.map((utxo, index) => {
|
|
309
|
+
const unlockScript = index > 0 && useSameAsPrevious ? '03' : unlockScript1
|
|
310
|
+
return { outputRef: utxo.ref, unlockScript }
|
|
311
|
+
})
|
|
312
|
+
const unsignedTx: node.UnsignedTx = {
|
|
313
|
+
txId: '',
|
|
314
|
+
version: 0,
|
|
315
|
+
networkId: 4,
|
|
316
|
+
gasAmount: 100000,
|
|
317
|
+
gasPrice: (ONE_ALPH / 100000n).toString(),
|
|
318
|
+
inputs: inputs0.concat(inputs1),
|
|
319
|
+
fixedOutputs: [{
|
|
320
|
+
hint: 0,
|
|
321
|
+
key: '',
|
|
322
|
+
attoAlphAmount: (ONE_ALPH * 3n).toString(),
|
|
323
|
+
address: newAccount.address,
|
|
324
|
+
tokens: [],
|
|
325
|
+
lockTime: 0,
|
|
326
|
+
message: ''
|
|
327
|
+
}]
|
|
328
|
+
}
|
|
329
|
+
const txBytes = codec.unsignedTxCodec.encodeApiUnsignedTx(unsignedTx)
|
|
330
|
+
const signResult0 = await newAccount.signUnsignedTx({
|
|
331
|
+
signerAddress: newAccount.address,
|
|
332
|
+
unsignedTx: binToHex(txBytes)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
approveTx([OutputType.Base], true)
|
|
336
|
+
const signature1 = await app.signUnsignedTx(path, Buffer.from(txBytes))
|
|
337
|
+
expect(transactionVerifySignature(signResult0.txId, testAccount.publicKey, signature1)).toBe(true)
|
|
338
|
+
|
|
339
|
+
const submitResult = await nodeProvider.multisig.postMultisigSubmit({
|
|
340
|
+
unsignedTx: binToHex(txBytes),
|
|
341
|
+
signatures: [signResult0.signature, signature1]
|
|
342
|
+
})
|
|
343
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
344
|
+
const balance = await getALPHBalance(newAccount.address)
|
|
345
|
+
expect(balance).toEqual(ONE_ALPH * 3n)
|
|
346
|
+
|
|
347
|
+
await app.close()
|
|
348
|
+
}, 120000)
|
|
349
|
+
|
|
350
|
+
it('should test self transfer tx', async () => {
|
|
351
|
+
const transport = await createTransport()
|
|
352
|
+
const app = new AlephiumApp(transport)
|
|
353
|
+
const [testAccount] = await app.getAccount(path)
|
|
354
|
+
await transferToAddress(testAccount.address)
|
|
355
|
+
|
|
356
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
357
|
+
fromPublicKey: testAccount.publicKey,
|
|
358
|
+
destinations: [
|
|
359
|
+
{
|
|
360
|
+
address: testAccount.address,
|
|
361
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
approveTx([])
|
|
367
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
368
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
369
|
+
|
|
370
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
371
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
372
|
+
signature: signature
|
|
373
|
+
})
|
|
374
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
375
|
+
const balance = await getALPHBalance(testAccount.address)
|
|
376
|
+
expect(balance > (ONE_ALPH * 9n)).toEqual(true)
|
|
377
|
+
|
|
378
|
+
await app.close()
|
|
379
|
+
}, 12000)
|
|
380
|
+
|
|
381
|
+
it('should test script execution tx', async () => {
|
|
382
|
+
const transport = await createTransport()
|
|
383
|
+
const app = new AlephiumApp(transport)
|
|
384
|
+
const [testAccount] = await app.getAccount(path)
|
|
385
|
+
await transferToAddress(testAccount.address)
|
|
386
|
+
const buildTxResult = await nodeProvider.contracts.postContractsUnsignedTxDeployContract({
|
|
387
|
+
fromPublicKey: testAccount.publicKey,
|
|
388
|
+
bytecode: '00010c010000000002d38d0b3636020000'
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
setTimeout(() => skipBlindSigningWarning(), 1000)
|
|
392
|
+
await expect(app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))).rejects.toThrow()
|
|
393
|
+
|
|
394
|
+
await enableBlindSigning()
|
|
395
|
+
if (needToAutoApprove()) {
|
|
396
|
+
staxFlexApproveOnce().then(() => approveTx([]))
|
|
397
|
+
} else {
|
|
398
|
+
// waiting for blind signing setting to be enabled
|
|
399
|
+
await sleep(20000)
|
|
400
|
+
}
|
|
401
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
402
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
403
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
404
|
+
signature: signature
|
|
405
|
+
})
|
|
406
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
407
|
+
const details = await nodeProvider.transactions.getTransactionsDetailsTxid(submitResult.txId)
|
|
408
|
+
expect(details.scriptExecutionOk).toEqual(true)
|
|
409
|
+
|
|
410
|
+
await app.close()
|
|
411
|
+
}, 120000)
|
|
412
|
+
})
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const hw_transport_node_hid_1 = __importDefault(require("@ledgerhq/hw-transport-node-hid"));
|
|
7
|
-
const logs_1 = require("@ledgerhq/logs");
|
|
8
|
-
const blakejs_1 = __importDefault(require("blakejs"));
|
|
9
|
-
const web3_1 = require("@alephium/web3");
|
|
10
|
-
const src_1 = __importDefault(require("../src"));
|
|
11
|
-
describe.skip('Integration', () => {
|
|
12
|
-
const path = `m/44'/1234'/0'/0/0`;
|
|
13
|
-
// enable this for integration test
|
|
14
|
-
it('should test node', async () => {
|
|
15
|
-
const transport = await hw_transport_node_hid_1.default.open('');
|
|
16
|
-
(0, logs_1.listen)((log) => console.log(log));
|
|
17
|
-
const app = new src_1.default(transport);
|
|
18
|
-
const [account] = await app.getAccount(path);
|
|
19
|
-
console.log(`${JSON.stringify(account)}`);
|
|
20
|
-
const hash = Buffer.from(blakejs_1.default.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32));
|
|
21
|
-
const signature = await app.signHash(path, hash);
|
|
22
|
-
console.log(signature);
|
|
23
|
-
expect((0, web3_1.transactionVerifySignature)(hash.toString('hex'), account.publicKey, signature)).toBe(true);
|
|
24
|
-
await app.close();
|
|
25
|
-
}, 100000);
|
|
26
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|