-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtx.ts
More file actions
302 lines (278 loc) · 8.48 KB
/
tx.ts
File metadata and controls
302 lines (278 loc) · 8.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// Copyright (C) 2021 Edge Network Technologies Limited
// Use of this source code is governed by a GNU GPL-style license
// that can be found in the LICENSE.md file. All rights reserved.
import { SHA256 } from 'crypto-js'
import { generateSignature } from './wallet'
import superagent from 'superagent'
import { toQueryString } from './helpers'
import { ListResponse, RequestCallback } from '.'
/**
* API response for creating on-chain transactions.
*
* See `createTransactions()` for more usage information.
*/
export type CreateResponse = {
results: CreateTxReceipt[]
metadata: {
accepted?: number
ignored?: number
rejected?: number
}
}
/**
* Receipt for the creation of an on-chain transaction.
*/
export type CreateTxReceipt = Partial<Tx> & {
success: boolean
status: number
reason?: string
balance?: number
transaction_nonce?: number
wallet_nonce?: number
transaction: Omit<Tx, 'hash'>
}
/**
* Possible device actions that can be added to transaction data.
*
* See the `Tx` type and `createTransactions()` for more information.
*/
export type DeviceAction = 'assign_device' | 'unassign_device'
/**
* Possible governance actions that can be added to transaction data.
*
* See the `Tx` type and `createTransactions()` for more information.
*/
export type GovernanceAction = 'create_proposal' | 'proposal_comment' | 'proposal_vote'
/**
* Pre-chain, signed transaction.
* This includes everything except the hash.
*/
export type SignedTx = Omit<Tx, 'hash'>
/**
* Possible stake actions that can be added to transaction data.
*
* See the `Tx` type and `createTransactions()` for more information.
*/
export type StakeAction = 'create_stake' | 'release_stake' | 'unlock_stake'
/**
* On-chain transaction.
*/
export type Tx = {
timestamp: number
sender: string
recipient: string
amount: number
data: TxData
nonce: number
hash: string
signature: string
}
/**
* Bridge transaction data.
* These values should only be set in exchange transactions created by Bridge.
*/
export type TxBridgeData = {
/** Ethereum address for exchange (withdrawal/sale) transaction. Used by Bridge. */
destination?: string
/** Fee amount in an exchange transaction. Used by Bridge. */
fee?: number
/** Exchange token. Used by Bridge. */
token?: string
}
/**
* Transaction data.
*/
export type TxData = TxBridgeData & TxGovernanceData & TxVarData & {
/** Blockchain action to be effected in the course of creating the transaction. */
action?: DeviceAction | GovernanceAction | StakeAction | VarAction
/** Device ID. Use with `action: "assign_device" | "unassign_device"` */
device?: string
/** Express unlock flag. Use with `action: "unlock_stake"` */
express?: boolean
/** Reference hash. Used by external systems. */
ref?: string
/** Transaction memo. */
memo?: string
/**
* Signature for other data - unrelated to transaction signature.
* Use with `action: "assign_device"` to sign the device address with its key.
*/
signature?: string
/** Stake hash. Use with `action: "assign_device" | "release_stake" | "unassign_device" | "unlock_stake"` */
stake?: string
}
/**
* Governance transaction data.
*/
export type TxGovernanceData = {
/** Content hash. Use with `action: "create_proposal"` or `action: "proposal_comment"` */
content?: string
/** Proposal hash. Use with `action: "proposal_comment"` or `action: "proposal_vote"` */
proposal?: string
/** Vote option. */
vote?: number
}
/**
* Variables transaction data.
* These values should only be set by a blockchain custodian when updating on-chain variables.
*/
export type TxVarData = {
/** Variable name. Use with `action: VarAction` */
key?: string
/** Variable value. Use with `action: "set_var"` */
value?: unknown
}
/**
* Parameters for a transactions query.
*
* Both `from` and `to` reflect block height.
*/
export type TxsParams = {
from?: number
to?: number
}
/**
* Pre-chain transaction that needs to be signed.
*/
export type UnsignedTx = Omit<Tx, 'hash' | 'signature'> & Partial<Pick<Tx, 'signature'>>
/**
* Possible variable setter actions. These are only usable by blockchain custodians.
*/
export type VarAction = 'set_var' | 'unset_var'
/**
* Create one or more transactions on chain.
*
* Transactions must be signed, otherwise they will be rejected.
* Wallet addresses are assumed to be correct; any validation should take place in user code.
*
* This function can also be used for staking transactions, by setting for example `data: { action: 'create_stake' }`.
* Refer to staking documentation and the StakeAction type for more detail.
*
* ```
* const myTx = sign({
* timestamp: Date.now(),
* sender: 'my-wallet-address',
* recipient: 'other-wallet-address',
* amount: 1000,
* data: { memo: 'example of sending 1 XE' },
* nonce: 1
* }, 'my-private-key')
*
* const res = await createTransactions('https://api.xe.network', [myTx])
* ```
*/
export const createTransactions =
async (host: string, txs: SignedTx[], cb?: RequestCallback): Promise<CreateResponse> => {
const req = superagent.post(`${host}/transaction`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send(txs)
if (cb !== undefined) cb(req)
const res = cb === undefined ? await req : await cb(req)
return res.body
}
/**
* Hash a signed transaction.
*
* When using this function, consider the input (signed) transaction to be 'consumed', and use only the hashed
* transaction that is returned.
* The hashed transaction should not be modified, otherwise its hash may be invalidated.
*/
export const hash = (tx: SignedTx): Tx => {
const [, message] = hashable(tx)
const h = SHA256(message).toString()
return { ...tx, hash: h }
}
/**
* Prepare a hashable transaction and hashing message.
*
* Normally, user code should just use `hash()`.
*/
export const hashable = (tx: SignedTx): [SignedTx, string] => {
const controlTx: SignedTx = {
timestamp: tx.timestamp,
sender: tx.sender,
recipient: tx.recipient,
amount: tx.amount,
data: tx.data,
nonce: tx.nonce,
signature: tx.signature
}
return [controlTx, JSON.stringify(controlTx)]
}
/**
* Get pending transactions.
*
* Pass a wallet address to get only pending transactions from that address.
*
* ```
* const allPendingTxs = await pendingTransactions('https://api.xe.network')
*
* const myPendingTxs = await pendingTransactions('https://api.xe.network', 'my-wallet-address')
* ```
*/
export const pendingTransactions = async (host: string, address?: string, cb?: RequestCallback): Promise<Tx[]> => {
let url = `${host}/transactions/pending`
if (address !== undefined) url += `/${address}`
const req = superagent.get(url)
const res = cb === undefined ? await req : await cb(req)
return res.body
}
/**
* Sign a transaction with a wallet private key.
*
* When using this function, consider the input (unsigned) transaction to be 'consumed', and use only the signed
* transaction that is returned.
* The signed transaction should not be modified, otherwise its signature may be invalidated.
*
* ```
* const myTx = sign({
* timestamp: Date.now(),
* sender: 'my-wallet-address',
* recipient: 'other-wallet-address',
* amount: 1000,
* data: { memo: 'example of sending 1 XE' },
* nonce: 1
* }, 'my-private-key')
* ```
*/
export const sign = (tx: UnsignedTx, privateKey: string): SignedTx => {
const [controlTx, message] = signable(tx)
controlTx.signature = generateSignature(privateKey, message)
return controlTx as SignedTx
}
/**
* Prepare a signable transaction and signing message.
*
* Normally, user code should just use `sign()`.
*/
export const signable = (tx: UnsignedTx): [UnsignedTx, string] => {
const controlTx: UnsignedTx = {
timestamp: tx.timestamp,
sender: tx.sender,
recipient: tx.recipient,
amount: tx.amount,
data: tx.data,
nonce: tx.nonce
}
return [controlTx, JSON.stringify(controlTx)]
}
/**
* Get recent transactions, or transactions within a specified block range.
*
* ```
* const recent = await tx.transactions('https://api.xe.network')
* const hist = await tx.transactions('https://api.xe.network', { from: 159335, to: 159345 })
* ```
*/
export const transactions = async (
host: string,
params?: TxsParams,
cb?: RequestCallback
): Promise<ListResponse<Tx>> => {
let url = `${host}/transactions`
if (params !== undefined) url += `?${toQueryString(params)}`
const req = superagent.get(url)
const res = cb === undefined ? await req : await cb(req)
return res.body
}