chain-sdk 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,472 @@
1
+ require 'securerandom'
2
+ require 'time'
3
+
4
+ require_relative './client_module'
5
+ require_relative './query'
6
+ require_relative './response_object'
7
+
8
+ module Chain
9
+ class Transaction < ResponseObject
10
+
11
+ # @!attribute [r] id
12
+ # Unique transaction identifier.
13
+ # @return [String]
14
+ attrib :id
15
+
16
+ # @!attribute [r] timestamp
17
+ # Time of transaction.
18
+ # @return [Time]
19
+ attrib(:timestamp) { |raw| Time.parse(raw) }
20
+
21
+ # @!attribute [r] block_id
22
+ # Unique identifier, or block hash, of the block containing a transaction.
23
+ # @return [String]
24
+ attrib :block_id
25
+
26
+ # @!attribute [r] block_height
27
+ # Height of the block containing a transaction.
28
+ # @return [Integer]
29
+ attrib :block_height
30
+
31
+ # @!attribute [r] position
32
+ # Position of a transaction within the block.
33
+ # @return [Integer]
34
+ attrib :position
35
+
36
+ # @!attribute [r] reference_data
37
+ # User specified, unstructured data embedded within a transaction.
38
+ # @return [Hash]
39
+ attrib :reference_data
40
+
41
+ # @!attribute [r] is_local
42
+ # A flag indicating one or more inputs or outputs are local.
43
+ # @return [Boolean]
44
+ attrib :is_local
45
+
46
+ # @!attribute [r] inputs
47
+ # List of specified inputs for a transaction.
48
+ # @return [Array<Input>]
49
+ attrib(:inputs) { |raw| raw.map { |v| Input.new(v) } }
50
+
51
+ # @!attribute [r] outputs
52
+ # List of specified outputs for a transaction.
53
+ # @return [Array<Output>]
54
+ attrib(:outputs) { |raw| raw.map { |v| Output.new(v) } }
55
+
56
+ class ClientModule < Chain::ClientModule
57
+ # @param [Builder] builder
58
+ # @yield Block defining transaction actions.
59
+ # @return [Template]
60
+ def build(builder = nil, &block)
61
+ if builder.nil?
62
+ builder = Builder.new(&block)
63
+ end
64
+
65
+ client.conn.singleton_batch_request(
66
+ 'build-transaction',
67
+ [builder]
68
+ ) { |item| Template.new(item) }
69
+ end
70
+
71
+ # @param [Array<Builder>] builders
72
+ # @return [Array<Template>]
73
+ def build_batch(builders)
74
+ client.conn.batch_request(
75
+ 'build-transaction',
76
+ builders
77
+ ) { |item| Template.new(item) }
78
+ end
79
+
80
+ # @param [Template] template
81
+ # @return [SubmitResponse]
82
+ def submit(template)
83
+ client.conn.singleton_batch_request(
84
+ 'submit-transaction',
85
+ {transactions: [template]}
86
+ ) { |item| SubmitResponse.new(item) }
87
+ end
88
+
89
+ # @param [Array<Template>] templates
90
+ # @return [Array<SubmitResponse>]
91
+ def submit_batch(templates)
92
+ client.conn.batch_request(
93
+ 'submit-transaction',
94
+ {transactions: templates}
95
+ ) { |item| SubmitResponse.new(item) }
96
+ end
97
+
98
+ # @param [Hash] query
99
+ # @return [Query]
100
+ def query(query = {})
101
+ Query.new(client, query)
102
+ end
103
+ end
104
+
105
+ class Query < Chain::Query
106
+ def fetch(query)
107
+ client.conn.request('list-transactions', query)
108
+ end
109
+
110
+ def translate(raw)
111
+ Transaction.new(raw)
112
+ end
113
+ end
114
+
115
+ class Input < ResponseObject
116
+ # @!attribute [r] type
117
+ # The type of the input.
118
+ #
119
+ # Possible values are "issue", "spend".
120
+ # @return [String]
121
+ attrib :type
122
+
123
+ # @!attribute [r] asset_id
124
+ # The id of the asset being issued or spent.
125
+ # @return [String]
126
+ attrib :asset_id
127
+
128
+ # @!attribute [r] asset_alias
129
+ # The alias of the asset being issued or spent (possibly null).
130
+ # @return [String]
131
+ attrib :asset_alias
132
+
133
+ # @!attribute [r] asset_definition
134
+ # The definition of the asset being issued or spent (possibly null).
135
+ # @return [Hash]
136
+ attrib :asset_definition
137
+
138
+ # @!attribute [r] asset_tags
139
+ # The tags of the asset being issued or spent (possibly null).
140
+ # @return [Hash]
141
+ attrib :asset_tags
142
+
143
+ # @!attribute [r] asset_is_local
144
+ # A flag indicating whether the asset being issued or spent is local.
145
+ # @return [Boolean]
146
+ attrib :asset_is_local
147
+
148
+ # @!attribute [r] amount
149
+ # The number of units of the asset being issued or spent.
150
+ # @return [Integer]
151
+ attrib :amount
152
+
153
+ # @!attribute [r] spent_output
154
+ # The output consumed by this input.
155
+ # @return [SpentOutput]
156
+ attrib(:spent_output) { |raw| SpentOutput.new(raw) }
157
+
158
+ # @!attribute [r] account_id
159
+ # The id of the account transferring the asset (possibly null if the
160
+ # input is an issuance or an unspent output is specified).
161
+ # @return [String]
162
+ attrib :account_id
163
+
164
+ # @!attribute [r] account_alias
165
+ # The alias of the account transferring the asset (possibly null if the
166
+ # input is an issuance or an unspent output is specified).
167
+ # @return [String]
168
+ attrib :account_alias
169
+
170
+ # @!attribute [r] account_tags
171
+ # The tags associated with the account (possibly null).
172
+ # @return [String]
173
+ attrib :account_tags
174
+
175
+ # @!attribute [r] input_witness
176
+ # @return [String]
177
+ attrib :input_witness
178
+
179
+ # @!attribute [r] issuance_program
180
+ # A program specifying a predicate for issuing an asset (possibly null
181
+ # if input is not an issuance).
182
+ # @return [String]
183
+ attrib :issuance_program
184
+
185
+ # @!attribute [r] control_program
186
+ # @return [String]
187
+ attrib :control_program
188
+
189
+ # @!attribute [r] reference_data
190
+ # User specified, unstructured data embedded within an input
191
+ # (possibly null).
192
+ # @return [Hash]
193
+ attrib :reference_data
194
+
195
+ # @!attribute [r] is_local
196
+ # A flag indicating if the input is local.
197
+ # @return [Boolean]
198
+ attrib :is_local
199
+
200
+ class SpentOutput < ResponseObject
201
+ # @!attribute [r] transaction_id
202
+ # Unique transaction identifier.
203
+ # @return [String]
204
+ attrib :transaction_id
205
+
206
+ # @!attribute [r] position
207
+ # Position of an output within the transaction.
208
+ # @return [Integer]
209
+ attrib :position
210
+ end
211
+ end
212
+
213
+ class Output < ResponseObject
214
+ # @!attribute [r] type
215
+ # The type of the output.
216
+ #
217
+ # Possible values are "control" and "retire".
218
+ # @return [String]
219
+ attrib :type
220
+
221
+ # @!attribute [r] purpose
222
+ # The purpose of the output.
223
+ #
224
+ # Possible values are "receive" and "change".
225
+ # @return [String]
226
+ attrib :purpose
227
+
228
+ # @!attribute [r] position
229
+ # The output's position in a transaction's list of outputs.
230
+ # @return [Integer]
231
+ attrib :position
232
+
233
+ # @!attribute [r] asset_id
234
+ # The id of the asset being controlled.
235
+ # @return [String]
236
+ attrib :asset_id
237
+
238
+ # @!attribute [r] asset_alias
239
+ # The alias of the asset being controlled (possibly null).
240
+ # @return [String]
241
+ attrib :asset_alias
242
+
243
+ # @!attribute [r] asset_definition
244
+ # The definition of the asset being controlled (possibly null).
245
+ # @return [Hash]
246
+ attrib :asset_definition
247
+
248
+ # @!attribute [r] asset_tags
249
+ # The tags of the asset being controlled (possibly null).
250
+ # @return [Hash]
251
+ attrib :asset_tags
252
+
253
+ # @!attribute [r] asset_is_local
254
+ # A flag indicating whether the asset being controlled is local.
255
+ # @return [Boolean]
256
+ attrib :asset_is_local
257
+
258
+ # @!attribute [r] amount
259
+ # The number of units of the asset being controlled.
260
+ # @return [Integer]
261
+ attrib :amount
262
+
263
+ # @!attribute [r] account_id
264
+ # The id of the account controlling this output (possibly null if a
265
+ # control program is specified).
266
+ # @return [String]
267
+ attrib :account_id
268
+
269
+ # @!attribute [r] account_alias
270
+ # The alias of the account controlling this output (possibly null if
271
+ # a control program is specified).
272
+ # @return [String]
273
+ attrib :account_alias
274
+
275
+ # @!attribute [r] account_tags
276
+ # The tags associated with the account controlling this output
277
+ # (possibly null if a control program is specified).
278
+ # @return [Hash]
279
+ attrib :account_tags
280
+
281
+ # @!attribute [r] control_program
282
+ # The control program which must be satisfied to transfer this output.
283
+ # @return [String]
284
+ attrib :control_program
285
+
286
+ # @!attribute [r] reference_data
287
+ # User specified, unstructured data embedded within an input
288
+ # (possibly null).
289
+ # @return [Hash]
290
+ attrib :reference_data
291
+
292
+ # @!attribute [r] is_local
293
+ # A flag indicating if the output is local.
294
+ # @return [Boolean]
295
+ attrib :is_local
296
+ end
297
+
298
+ class Builder
299
+ def initialize(&block)
300
+ block.call(self) if block
301
+ end
302
+
303
+ # @return [Array<Hash>]
304
+ def actions
305
+ @actions ||= []
306
+ end
307
+
308
+ # @param [Template, String] template_or_raw_tx
309
+ # @return [Builder]
310
+ def base_transaction(template_or_raw_tx)
311
+ if template_or_raw_tx.is_a?(Transaction::Template)
312
+ @base_transaction = template_or_raw_tx.raw_transaction
313
+ else
314
+ @base_transaction = template_or_raw_tx
315
+ end
316
+ self
317
+ end
318
+
319
+ # @return [Builder]
320
+ def ttl(ttl)
321
+ @ttl = ttl
322
+ self
323
+ end
324
+
325
+ # @return [Hash]
326
+ def to_h
327
+ {
328
+ actions: actions,
329
+ base_transaction: @base_transaction,
330
+ ttl: @ttl,
331
+ }
332
+ end
333
+
334
+ # @return [String]
335
+ def to_json(opts = nil)
336
+ to_h.to_json(opts)
337
+ end
338
+
339
+ # Add an action to the tranasction builder
340
+ # @param [Hash] params Action parameters containing a type field and the
341
+ # required parameters for that type
342
+ # @return [Builder]
343
+ def add_action(params)
344
+ # Some actions require an idempotency token, so we'll add it here as a
345
+ # generic parameter.
346
+ params = {client_token: SecureRandom.uuid}.merge(params)
347
+ actions << params
348
+ self
349
+ end
350
+
351
+ # Sets the transaction-level reference data.
352
+ # May only be used once per transaction.
353
+ # @param [Hash] reference_data User specified, unstructured data to
354
+ # be embedded in a transaction
355
+ # @return [Builder]
356
+ def transaction_reference_data(reference_data)
357
+ add_action(
358
+ type: :set_transaction_reference_data,
359
+ reference_data: reference_data,
360
+ )
361
+ end
362
+
363
+ # Add an issuance action.
364
+ # @param [Hash] params Action parameters
365
+ # @option params [String] :asset_id Asset ID specifiying the asset to be issued.
366
+ # You must specify either an ID or an alias.
367
+ # @option params [String] :asset_alias Asset alias specifying the asset to be issued.
368
+ # You must specify either an ID or an alias.
369
+ # @option params [Integer] :amount amount of the asset to be issued
370
+ # @return [Builder]
371
+ def issue(params)
372
+ add_action(params.merge(type: :issue))
373
+ end
374
+
375
+ # Add a spend action taken on a particular account.
376
+ # @param [Hash] params Action parameters
377
+ # @option params [String] :asset_id Asset ID specifiying the asset to be spent.
378
+ # You must specify either an ID or an alias.
379
+ # @option params [String] :asset_alias Asset alias specifying the asset to be spent.
380
+ # You must specify either an ID or an alias.
381
+ # @option params [String] :account_id Account ID specifiying the account spending the asset.
382
+ # You must specify either an ID or an alias.
383
+ # @option params [String] :account_alias Account alias specifying the account spending the asset.
384
+ # You must specify either an ID or an alias.
385
+ # @option params [Integer] :amount amount of the asset to be spent.
386
+ # @return [Builder]
387
+ def spend_from_account(params)
388
+ add_action(params.merge(type: :spend_account))
389
+ end
390
+
391
+ # Add a spend action taken on a particular unspent output.
392
+ # @param [Hash] params Action parameters
393
+ # @option params [String] :transaction_id Transaction ID specifying the tranasction to select an output from.
394
+ # @option params [Integer] :position Position of the output within the transaction to be spent.
395
+ # @return [Builder]
396
+ def spend_account_unspent_output(params)
397
+ add_action(params.merge(type: :spend_account_unspent_output))
398
+ end
399
+
400
+ # Add a control action taken on a particular account.
401
+ # @param [Hash] params Action parameters
402
+ # @option params [String] :asset_id Asset ID specifiying the asset to be controlled.
403
+ # You must specify either an ID or an alias.
404
+ # @option params [String] :asset_alias Asset alias specifying the asset to be controlled.
405
+ # You must specify either an ID or an alias.
406
+ # @option params [String] :account_id Account ID specifiying the account controlling the asset.
407
+ # You must specify either an ID or an alias.
408
+ # @option params [String] :account_alias Account alias specifying the account controlling the asset.
409
+ # You must specify either an ID or an alias.
410
+ # @option params [Integer] :amount amount of the asset to be controlled.
411
+ # @return [Builder]
412
+ def control_with_account(params)
413
+ add_action(params.merge(type: :control_account))
414
+ end
415
+
416
+ # Add a control action taken on a control program.
417
+ # @param [Hash] params Action parameters
418
+ # @option params [String] :asset_id Asset ID specifiying the asset to be controlled.
419
+ # You must specify either an ID or an alias.
420
+ # @option params [String] :asset_alias Asset alias specifying the asset to be controlled.
421
+ # You must specify either an ID or an alias.
422
+ # @option params [String] :control_program The control program to be used
423
+ # @option params [Integer] :amount amount of the asset to be controlled.
424
+ # @return [Builder]
425
+ def control_with_program(params)
426
+ add_action(params.merge(type: :control_program))
427
+ end
428
+
429
+ # Add a retire action.
430
+ # @param [Hash] params Action parameters
431
+ # @option params [String] :asset_id Asset ID specifiying the asset to be retired.
432
+ # You must specify either an ID or an alias.
433
+ # @option params [String] :asset_alias Asset alias specifying the asset to be retired.
434
+ # You must specify either an ID or an alias.
435
+ # @option params [Integer] :amount Amount of the asset to be retired.
436
+ # @return [Builder]
437
+ def retire(params)
438
+ add_action(params.merge(
439
+ type: :control_program,
440
+ control_program: '6a'
441
+ ))
442
+ end
443
+ end
444
+
445
+ class SubmitResponse < ResponseObject
446
+ # @!attribute [r] id
447
+ # @return [String]
448
+ attrib :id
449
+ end
450
+
451
+ class Template < ResponseObject
452
+ # @!attribute [r] raw_transaction
453
+ # @return [String]
454
+ attrib :raw_transaction
455
+
456
+ # @!attribute [r] signing_instructions
457
+ # @return [String]
458
+ attrib :signing_instructions
459
+
460
+ # @return [Template]
461
+ def allow_additional_actions
462
+ @allow_additional_actions = true
463
+ self
464
+ end
465
+
466
+ # @return [Hash]
467
+ def to_h
468
+ super.merge(allow_additional_actions: @allow_additional_actions)
469
+ end
470
+ end
471
+ end
472
+ end
@@ -0,0 +1,100 @@
1
+ require 'securerandom'
2
+
3
+ require_relative './client_module'
4
+ require_relative './connection'
5
+ require_relative './constants'
6
+ require_relative './response_object'
7
+
8
+ module Chain
9
+ class TransactionFeed < ResponseObject
10
+
11
+ # @!attribute [r] id
12
+ # Unique transaction feed identifier.
13
+ # @return [String]
14
+ attrib :id
15
+
16
+ # @!attribute [r] alias
17
+ # User specified, unique identifier.
18
+ # @return [String]
19
+ attrib :alias
20
+
21
+ # @!attribute [r] filter
22
+ # @return [String]
23
+ attrib :filter
24
+
25
+ # @!attribute [r] after
26
+ # @return [String]
27
+ attrib :after
28
+
29
+ def initialize(raw_attribs, base_conn)
30
+ super(raw_attribs)
31
+
32
+ # The consume/ack cycle should run on its own thread, so make a copy of
33
+ # the base connection so this feed has an exclusive HTTP connection.
34
+ @conn = Connection.new(base_conn.opts)
35
+ end
36
+
37
+ # @param [Fixnum] timeout value in seconds
38
+ # @yield [Transaction] block process individual transactions
39
+ # @yieldparam [Transaction] tx
40
+ # @return [void]
41
+ def consume(timeout: 24*60*60)
42
+ query = {
43
+ filter: filter,
44
+ after: after,
45
+ timeout: (timeout * 1000).to_i, # milliseconds
46
+ ascending_with_long_poll: true
47
+ }
48
+
49
+ longpoll = Connection.new(@conn.opts.merge(read_timeout: timeout))
50
+
51
+ loop do
52
+ page = longpoll.request('list-transactions', query)
53
+ query = page['next']
54
+
55
+ page['items'].each do |raw_tx|
56
+ tx = Transaction.new(raw_tx)
57
+
58
+ # Memoize the cursor value for this transaction in case the user
59
+ # decides to ack. The format of the cursor value is specified in the
60
+ # core/query package.
61
+ @next_after = "#{tx.block_height}:#{tx.position}-#{MAX_BLOCK_HEIGHT}"
62
+
63
+ yield tx
64
+ end
65
+ end
66
+ end
67
+
68
+ def ack
69
+ raise 'ack must be called at most once per cycle in a consume loop' unless @next_after
70
+
71
+ @conn.request(
72
+ 'update-transaction-feed',
73
+ id: id,
74
+ after: @next_after,
75
+ previous_after: after,
76
+ )
77
+
78
+ self.after = @next_after
79
+ @next_after = nil
80
+ end
81
+
82
+ class ClientModule < Chain::ClientModule
83
+
84
+ # @param [Hash] opts
85
+ # @return [TransactionFeed]
86
+ def create(opts)
87
+ opts = {client_token: SecureRandom.uuid()}.merge(opts)
88
+ TransactionFeed.new(client.conn.request('create-transaction-feed', opts), client.conn)
89
+ end
90
+
91
+ # @param [Hash] opts
92
+ # @return [TransactionFeed]
93
+ def get(opts)
94
+ TransactionFeed.new(client.conn.request('get-transaction-feed', opts), client.conn)
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,90 @@
1
+ require_relative './client_module'
2
+ require_relative './response_object'
3
+ require_relative './query'
4
+
5
+ module Chain
6
+ class UnspentOutput < ResponseObject
7
+
8
+ # @!attribute [r] type
9
+ # @return [String]
10
+ attrib :type
11
+
12
+ # @!attribute [r] purpose
13
+ # @return [String]
14
+ attrib :purpose
15
+
16
+ # @!attribute [r] transaction_id
17
+ # @return [String]
18
+ attrib :transaction_id
19
+
20
+ # @!attribute [r] position
21
+ # @return [Integer]
22
+ attrib :position
23
+
24
+ # @!attribute [r] asset_id
25
+ # @return [String]
26
+ attrib :asset_id
27
+
28
+ # @!attribute [r] asset_alias
29
+ # @return [String]
30
+ attrib :asset_alias
31
+
32
+ # @!attribute [r] asset_definition
33
+ # @return [Hash]
34
+ attrib :asset_definition
35
+
36
+ # @!attribute [r] asset_tags
37
+ # @return [Hash]
38
+ attrib :asset_tags
39
+
40
+ # @!attribute [r] asset_is_local
41
+ # @return [Boolean]
42
+ attrib :asset_is_local
43
+
44
+ # @!attribute [r] amount
45
+ # @return [Integer]
46
+ attrib :amount
47
+
48
+ # @!attribute [r] account_id
49
+ # @return [String]
50
+ attrib :account_id
51
+
52
+ # @!attribute [r] account_alias
53
+ # @return [String]
54
+ attrib :account_alias
55
+
56
+ # @!attribute [r] account_tags
57
+ # @return [Hash]
58
+ attrib :account_tags
59
+
60
+ # @!attribute [r] control_program
61
+ # @return [String]
62
+ attrib :control_program
63
+
64
+ # @!attribute [r] reference_data
65
+ # @return [Hash]
66
+ attrib :reference_data
67
+
68
+ # @!attribute [r] is_local
69
+ # @return [Boolean]
70
+ attrib :is_local
71
+
72
+ class ClientModule < Chain::ClientModule
73
+ # @param [Hash] query
74
+ # @return Query
75
+ def query(query = {})
76
+ Query.new(client, query)
77
+ end
78
+ end
79
+
80
+ class Query < Chain::Query
81
+ def fetch(query)
82
+ client.conn.request('list-unspent-outputs', query)
83
+ end
84
+
85
+ def translate(raw)
86
+ UnspentOutput.new(raw)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module Chain
2
+ VERSION = '1.0.0.pre'
3
+ end
data/lib/chain.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative './chain/client'
2
+ require_relative './chain/constants'
3
+ require_relative './chain/version'