@alephium/ledger-app 0.2.0 → 0.3.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.
@@ -0,0 +1,55 @@
1
+ # Import this mnemonic to have 4'000'000 token allocated for your addresses
2
+ #
3
+ # vault alarm sad mass witness property virus style good flower rice alpha viable evidence run glare pretty scout evil judge enroll refuse another lava
4
+
5
+ alephium.genesis.allocations = [
6
+ {
7
+ address = "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH",
8
+ amount = 1000000000000000000000000,
9
+ lock-duration = 0 seconds
10
+ },
11
+ {
12
+ address = "14UAjZ3qcmEVKdTo84Kwf4RprTQi86w2TefnnGFjov9xF",
13
+ amount = 1000000000000000000000000,
14
+ lock-duration = 0 seconds
15
+ },
16
+ {
17
+ address = "15jjExDyS8q3Wqk9v29PCQ21jDqubDrD8WQdgn6VW2oi4",
18
+ amount = 1000000000000000000000000,
19
+ lock-duration = 0 seconds
20
+ },
21
+ {
22
+ address = "17cBiTcWhung3WDLuc9ja5Y7BMus5Q7CD9wYBxS1r1P2R",
23
+ amount = 1000000000000000000000000,
24
+ lock-duration = 0 seconds
25
+ }
26
+ ]
27
+ alephium.consensus.num-zeros-at-least-in-hash = 0
28
+ alephium.consensus.mainnet.block-target-time = 10 millis
29
+ alephium.consensus.mainnet.uncle-dependency-gap-time = 0 seconds
30
+ alephium.consensus.rhone.block-target-time = 5 millis
31
+ alephium.consensus.rhone.uncle-dependency-gap-time = 0 seconds
32
+ alephium.network.leman-hard-fork-timestamp = 0
33
+ alephium.network.rhone-hard-fork-timestamp = 0
34
+
35
+ alephium.network.network-id = 4
36
+ alephium.discovery.bootstrap = []
37
+ alephium.wallet.locking-timeout = 99999 minutes
38
+ alephium.mempool.auto-mine-for-dev = true
39
+ alephium.node.event-log.enabled=true
40
+ alephium.node.event-log.index-by-tx-id = true
41
+ alephium.node.event-log.index-by-block-hash = true
42
+
43
+ alephium.network.rest-port = 22973
44
+ alephium.network.ws-port = 21973
45
+ alephium.network.miner-api-port = 20973
46
+ alephium.api.network-interface = "0.0.0.0"
47
+ alephium.api.api-key-enabled = false
48
+
49
+ # arbitrary mining addresses
50
+ alephium.mining.miner-addresses = [
51
+ "1FsroWmeJPBhcPiUr37pWXdojRBe6jdey9uukEXk1TheA",
52
+ "1CQvSXsmM5BMFKguKDPpNUfw1idiut8UifLtT8748JdHc",
53
+ "193maApeJWrz9GFwWCfa982ccLARVE9Y1WgKSJaUs7UAx",
54
+ "16fZKYPCZJv2TP3FArA9FLUQceTS9U8xVnSjxFG9MBKyY"
55
+ ]
@@ -0,0 +1,22 @@
1
+ version: "3.3"
2
+
3
+ services:
4
+ alephium:
5
+ image: alephium/alephium:v3.3.0
6
+ restart: "no"
7
+ ports:
8
+ - 19973:19973/tcp
9
+ - 19973:19973/udp
10
+ - 127.0.0.1:20973:20973
11
+ - 127.0.0.1:21973:21973
12
+ - 127.0.0.1:22973:22973
13
+ security_opt:
14
+ - no-new-privileges:true
15
+ volumes:
16
+ - ./devnet.conf:/alephium-home/.alephium/user.conf
17
+ # - ~/.alephium:/alephium-home/.alephium
18
+ environment:
19
+ ALEPHIUM_FILE_LOG_LEVEL: "DEBUG"
20
+ healthcheck:
21
+ test: ["CMD", "curl", "http://127.0.0.1:22973/infos/self-clique"]
22
+ timeout: 45s
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alephium/ledger-app",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "GPL",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "exports": {
@@ -25,19 +25,19 @@
25
25
  "trailingComma": "none"
26
26
  },
27
27
  "dependencies": {
28
- "@alephium/cli": "0.10.2",
29
- "@alephium/web3": "0.10.2",
30
- "@alephium/web3-test": "0.10.2",
31
- "@alephium/web3-wallet": "0.10.2",
32
- "@ledgerhq/hw-transport": "6.27.10"
28
+ "@alephium/web3": "^1.2.0",
29
+ "@alephium/web3-wallet": "^1.2.0",
30
+ "@ledgerhq/hw-transport": "^6.31.0"
33
31
  },
34
32
  "devDependencies": {
35
- "@ledgerhq/hw-transport-node-hid": "^6.27.9",
36
- "@ledgerhq/hw-transport-node-speculos": "^6.27.9",
33
+ "@alephium/web3-test": "^1.2.0",
34
+ "@alephium/cli": "^1.2.0",
35
+ "@ledgerhq/hw-transport-node-hid": "^6.29.1",
36
+ "@ledgerhq/hw-transport-node-speculos": "^6.29.0",
37
37
  "@ledgerhq/logs": "^6.10.1",
38
38
  "@types/elliptic": "^6.4.13",
39
39
  "@types/jest": "^27.5.1",
40
- "@types/node": "^16.7.8",
40
+ "@types/node": "^20.8.10",
41
41
  "@typescript-eslint/eslint-plugin": "^4.30.0",
42
42
  "@typescript-eslint/parser": "^4.30.0",
43
43
  "eslint": "^7.32.0",
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export const CLA = 0x80
9
9
  export enum INS {
10
10
  GET_VERSION = 0x00,
11
11
  GET_PUBLIC_KEY = 0x01,
12
- SIGN_HASH = 0x02
12
+ SIGN_TX = 0x02
13
13
  }
14
14
 
15
15
  export const GROUP_NUM = 4
@@ -53,22 +53,37 @@ export default class AlephiumApp {
53
53
  return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex] as const
54
54
  }
55
55
 
56
- async signHash(path: string, hash: Buffer): Promise<string> {
57
- if (hash.length !== HASH_LEN) {
58
- throw new Error('Invalid hash length')
56
+ async signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string> {
57
+ console.log(`unsigned tx size: ${unsignedTx.length}`)
58
+ const encodedPath = serde.serializePath(path)
59
+ const firstFrameTxLength = 256 - 25;
60
+ const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length)
61
+ const data = Buffer.concat([encodedPath, txData])
62
+ let response = await this.transport.send(CLA, INS.SIGN_TX, 0x00, 0x00, data, [StatusCodes.OK])
63
+ if (unsignedTx.length <= firstFrameTxLength) {
64
+ return decodeSignature(response)
59
65
  }
60
66
 
61
- const data = Buffer.concat([serde.serializePath(path), hash])
62
- console.log(`data ${data.length}`)
63
- const response = await this.transport.send(CLA, INS.SIGN_HASH, 0x00, 0x00, data, [StatusCodes.OK])
64
- console.log(`response ${response.length} - ${response.toString('hex')}`)
67
+ const frameLength = 256 - 5
68
+ let fromIndex = firstFrameTxLength
69
+ while (fromIndex < unsignedTx.length) {
70
+ const remain = unsignedTx.length - fromIndex
71
+ const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length
72
+ const data = unsignedTx.slice(fromIndex, toIndex)
73
+ response = await this.transport.send(CLA, INS.SIGN_TX, 0x01, 0x00, data, [StatusCodes.OK])
74
+ fromIndex = toIndex
75
+ }
65
76
 
66
- // Decode signature: https://bitcoin.stackexchange.com/a/12556
67
- const rLen = response.slice(3, 4)[0]
68
- const r = response.slice(4, 4 + rLen)
69
- const sLen = response.slice(5 + rLen, 6 + rLen)[0]
70
- const s = response.slice(6 + rLen, 6 + rLen + sLen)
71
- console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
72
- return encodeHexSignature(r.toString('hex'), s.toString('hex'))
77
+ return decodeSignature(response)
73
78
  }
74
79
  }
80
+
81
+ function decodeSignature(response: Buffer): string {
82
+ // Decode signature: https://bitcoin.stackexchange.com/a/12556
83
+ const rLen = response.slice(3, 4)[0]
84
+ const r = response.slice(4, 4 + rLen)
85
+ const sLen = response.slice(5 + rLen, 6 + rLen)[0]
86
+ const s = response.slice(6 + rLen, 6 + rLen + sLen)
87
+ console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
88
+ return encodeHexSignature(r.toString('hex'), s.toString('hex'))
89
+ }
@@ -1,28 +1,152 @@
1
1
  import NodeTransport from '@ledgerhq/hw-transport-node-hid'
2
2
  import { listen } from '@ledgerhq/logs'
3
- import blake from 'blakejs'
4
3
 
5
- import { transactionVerifySignature } from '@alephium/web3'
4
+ import { ALPH_TOKEN_ID, Address, NodeProvider, ONE_ALPH, transactionVerifySignature, web3, waitForTxConfirmation } from '@alephium/web3'
6
5
 
7
6
  import AlephiumApp from '../src'
7
+ import { getSigner, mintToken, transfer } from '@alephium/web3-test'
8
+ import { PrivateKeyWallet } from '@alephium/web3-wallet'
8
9
 
9
10
  describe.skip('Integration', () => {
10
11
  const path = `m/44'/1234'/0'/0/0`
12
+ const nodeProvider = new NodeProvider("http://127.0.0.1:22973")
13
+ web3.setCurrentNodeProvider(nodeProvider)
14
+
15
+ function randomP2PKHAddress(groupIndex: number): string {
16
+ return PrivateKeyWallet.Random(groupIndex, nodeProvider).address
17
+ }
18
+
19
+ async function getALPHBalance(address: Address) {
20
+ const balances = await nodeProvider.addresses.getAddressesAddressBalance(address)
21
+ return BigInt(balances.balance)
22
+ }
23
+
24
+ async function transferToTestAccount(address: Address) {
25
+ const fromAccount = await getSigner()
26
+ const transferResult = await transfer(fromAccount, address, ALPH_TOKEN_ID, ONE_ALPH * 10n)
27
+ await waitForTxConfirmation(transferResult.txId, 1, 1000)
28
+ }
11
29
 
12
30
  // enable this for integration test
13
- it('should test node', async () => {
31
+ it('should transfer ALPH', async () => {
32
+ const transport = await NodeTransport.open('')
33
+ listen((log) => console.log(log))
34
+ const app = new AlephiumApp(transport)
35
+ const [testAccount] = await app.getAccount(path)
36
+ await transferToTestAccount(testAccount.address)
37
+
38
+ const balance0 = await getALPHBalance(testAccount.address)
39
+ const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
40
+ fromPublicKey: testAccount.publicKey,
41
+ destinations: [
42
+ {
43
+ address: randomP2PKHAddress(0),
44
+ attoAlphAmount: (ONE_ALPH * 2n).toString(),
45
+ },
46
+ {
47
+ address: randomP2PKHAddress(0),
48
+ attoAlphAmount: (ONE_ALPH * 3n).toString(),
49
+ },
50
+ ]
51
+ })
52
+
53
+ const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
54
+ expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
55
+
56
+ const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
57
+ unsignedTx: buildTxResult.unsignedTx,
58
+ signature: signature
59
+ })
60
+ await waitForTxConfirmation(submitResult.txId, 1, 1000)
61
+ const balance1 = await getALPHBalance(testAccount.address)
62
+ const gasFee = BigInt(buildTxResult.gasAmount) * BigInt(buildTxResult.gasPrice)
63
+ expect(balance1).toEqual(balance0 - gasFee - ONE_ALPH * 5n)
64
+
65
+ await app.close()
66
+ }, 120000)
67
+
68
+ it('should transfer token', async () => {
69
+ const transport = await NodeTransport.open('')
70
+ listen((log) => console.log(log))
71
+ const app = new AlephiumApp(transport)
72
+ const [testAccount] = await app.getAccount(path)
73
+ await transferToTestAccount(testAccount.address)
74
+
75
+ const tokenAmount = 2222222222222222222222222n
76
+ const tokenInfo = await mintToken(testAccount.address, tokenAmount)
77
+ const balances0 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
78
+
79
+ const transferAmount = 2222222222222222222222222n / 2n
80
+ const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
81
+ fromPublicKey: testAccount.publicKey,
82
+ destinations: [
83
+ {
84
+ address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
85
+ attoAlphAmount: (ONE_ALPH * 2n).toString(),
86
+ tokens: [{ id: tokenInfo.contractId, amount: transferAmount.toString() }]
87
+ }
88
+ ]
89
+ })
90
+
91
+ const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
92
+ expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
93
+
94
+ const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
95
+ unsignedTx: buildTxResult.unsignedTx,
96
+ signature: signature
97
+ })
98
+ await waitForTxConfirmation(submitResult.txId, 1, 1000)
99
+ const balances1 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
100
+ const gasFee = BigInt(buildTxResult.gasAmount) * BigInt(buildTxResult.gasPrice)
101
+ const alphBalance = BigInt(balances1.balance)
102
+ expect(alphBalance).toEqual(BigInt(balances0.balance) - gasFee - ONE_ALPH * 2n)
103
+
104
+ const tokenBalance = balances1.tokenBalances!.find((t) => t.id === tokenInfo.contractId)!
105
+ expect(tokenBalance.amount).toEqual((tokenAmount - transferAmount).toString())
106
+
107
+ await app.close()
108
+ }, 120000)
109
+
110
+ it('should transfer to multisig address', async () => {
14
111
  const transport = await NodeTransport.open('')
15
112
  listen((log) => console.log(log))
16
113
  const app = new AlephiumApp(transport)
114
+ const [testAccount] = await app.getAccount(path)
115
+ await transferToTestAccount(testAccount.address)
116
+
117
+ const tokenAmount = 2222222222222222222222222n
118
+ const tokenInfo = await mintToken(testAccount.address, tokenAmount)
119
+ const balances0 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
120
+
121
+ const transferAmount = 2222222222222222222222222n / 2n
122
+ const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
123
+ const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
124
+ fromPublicKey: testAccount.publicKey,
125
+ destinations: [
126
+ {
127
+ address: multiSigAddress,
128
+ attoAlphAmount: (ONE_ALPH * 2n).toString(),
129
+ tokens: [{ id: tokenInfo.contractId, amount: transferAmount.toString() }]
130
+ }
131
+ ]
132
+ })
133
+
134
+ const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
135
+ expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
17
136
 
18
- const [account] = await app.getAccount(path)
19
- console.log(`${JSON.stringify(account)}`)
137
+ const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
138
+ unsignedTx: buildTxResult.unsignedTx,
139
+ signature: signature
140
+ })
141
+ await waitForTxConfirmation(submitResult.txId, 1, 1000)
142
+ const balances1 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
143
+ const gasFee = BigInt(buildTxResult.gasAmount) * BigInt(buildTxResult.gasPrice)
144
+ const alphBalance = BigInt(balances1.balance)
145
+ expect(alphBalance).toEqual(BigInt(balances0.balance) - gasFee - ONE_ALPH * 2n)
20
146
 
21
- const hash = Buffer.from(blake.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32))
22
- const signature = await app.signHash(path, hash)
23
- console.log(signature)
24
- expect(transactionVerifySignature(hash.toString('hex'), account.publicKey, signature)).toBe(true)
147
+ const tokenBalance = balances1.tokenBalances!.find((t) => t.id === tokenInfo.contractId)!
148
+ expect(tokenBalance.amount).toEqual((tokenAmount - transferAmount).toString())
25
149
 
26
150
  await app.close()
27
- }, 100000)
151
+ }, 120000)
28
152
  })