ic_agent 0.1.1

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,270 @@
1
+ require 'rubytree'
2
+
3
+ module IcAgent
4
+ class Canister
5
+ def initialize(agent, canister_id, candid=nil)
6
+ @agent = agent
7
+ @canister_id = canister_id
8
+ if candid
9
+ @candid = candid
10
+ else
11
+ candid = @agent.query_raw(canister_id, '__get_candid_interface_tmp_hack', encode([]))
12
+ @candid = candid[0]['value']
13
+ end
14
+ if candid.nil?
15
+ puts candid
16
+ puts 'Please provide candid description'
17
+ raise BaseException, "canister #{@canister_id} has no __get_candid_interface_tmp_hack method."
18
+ end
19
+
20
+ parser = IcAgent::Ast::Parser.new
21
+ parser.parse(@candid)
22
+
23
+ ic_service_methods = parser.ic_service_methods.elements
24
+ ic_service_methods.each do |item|
25
+ service_method = item.to_obj
26
+ method_name = service_method['ic_service_method_name']
27
+ anno = service_method['ic_service_method_query']
28
+ args = service_method['ic_service_method_params']
29
+ rets = service_method['ic_service_method_return']
30
+
31
+ args_arr = args.nil? ? [] : args.split(',').map(&:strip)
32
+ args_type_arrs = []
33
+ args_arr.each do |arg|
34
+ args_type_arrs << get_param_to_ic_type(parser, arg)
35
+ end
36
+ ret_type = get_param_to_ic_type(parser, rets)
37
+
38
+ add_caniter_method(method_name, args, args_type_arrs, ret_type, anno)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def service_param_child(parser, args_name, now_args = [], child_args = [], done_args = [])
45
+ arg_opt = args_name.include?(' ') ? args_name.split(' ')[0] : args_name
46
+ arg_child_item = []
47
+ if IcAgent::Candid::ALL_TYPES.include?(arg_opt)
48
+ arg_child_item = args_name.split(' ')[1..] if args_name.include?(' ')
49
+ else
50
+ input_body = parser.ic_type_by_name(arg_opt)
51
+ input_body_obj = input_body.to_obj
52
+ arg_child_item = input_body_obj['type_child_item_values']
53
+ done_args << arg_opt
54
+ end
55
+
56
+ child_args = child_args + arg_child_item.flatten - done_args - IcAgent::Candid::ALL_TYPES
57
+ child_args = child_args.uniq
58
+
59
+ add_childs = child_args - now_args
60
+ now_args += add_childs
61
+
62
+ if child_args.length > 0
63
+ next_args_name = child_args.pop
64
+ return service_param_child(parser, next_args_name, now_args, child_args, done_args)
65
+ end
66
+ now_args
67
+ end
68
+
69
+ def build_param_tree(parser, type_name, current_node = nil, tree_root_node = nil)
70
+ if current_node.nil?
71
+ root_type = parser.ic_type_by_name(type_name)
72
+ refer_nodes = root_type.type_child_refer_items.nil? ? [] : root_type.type_child_refer_items
73
+ root_node = Tree::TreeNode.new(type_name,
74
+ { 'total_child': refer_nodes.size,
75
+ 'all_child': refer_nodes,
76
+ 'child_refer': refer_nodes,
77
+ 'self_refer': false,
78
+ 'refer_path': [type_name],
79
+ 'ic_type': nil,
80
+ 'prototype': root_type.type_param_content,
81
+ 'content': root_type.type_param_content })
82
+ tree_root_node = root_node
83
+ else
84
+ root_node = current_node
85
+ refer_nodes = current_node.content[:child_refer]
86
+ end
87
+
88
+ if tree_root_node && current_node && tree_root_node.name != current_node.name && current_node.content[:child_refer].size == 0
89
+ parent_node = current_node.parent
90
+ parent_node.content[:child_refer].delete(current_node.name)
91
+ # update parent ic types
92
+ child_key_types = {}
93
+ current_node.children.each do |child_node|
94
+ child_key_types[child_node.name] = child_node.content[:ic_type]
95
+ end
96
+ ic_type = IcAgent::Ast::Assembler.build_type(current_node.content[:prototype], child_key_types)
97
+ current_node.content[:ic_type] = ic_type
98
+
99
+
100
+ # replace parent content
101
+ new_content = replace_root_child_code(parent_node.content[:content], current_node.name, current_node.content[:content])
102
+ parent_node.content[:content] = new_content
103
+
104
+ # clear refer_path
105
+ refer_index = tree_root_node.content[:refer_path].index(current_node.name)
106
+ tree_root_node.content[:refer_path] = tree_root_node.content[:refer_path].slice(0, refer_index) if refer_index
107
+ build_param_tree(parser, parent_node.name, parent_node, tree_root_node)
108
+ end
109
+
110
+ if tree_root_node.nil? || tree_root_node.content[:child_refer].size > 0
111
+ if refer_nodes.size > 0
112
+ refer_node = refer_nodes[0]
113
+ # self refer
114
+ if tree_root_node && tree_root_node.content[:refer_path].include?(refer_node)
115
+ child_node = Tree::TreeNode.new(refer_node, { 'total_child': 0, 'child_refer': [], 'self_refer': true, 'content': nil })
116
+ root_node << child_node
117
+ root_node.content[:child_refer].delete(refer_node)
118
+ build_param_tree(parser, root_node.name, root_node, tree_root_node)
119
+ else
120
+ # non self refer
121
+ child_type = parser.ic_type_by_name(refer_node)
122
+ child_refer_nodes = child_type.type_child_refer_items
123
+ if child_refer_nodes.size == 0
124
+ # set ic type
125
+ ic_type = IcAgent::Ast::Assembler.build_type(child_type.type_param_content)
126
+ child_node = Tree::TreeNode.new(refer_node, { 'total_child': 0,
127
+ 'child_refer': [],
128
+ 'self_refer': false,
129
+ 'ic_type': ic_type,
130
+ 'prototype': child_type.type_param_content,
131
+ 'content': child_type.type_param_content })
132
+ root_node << child_node
133
+ root_node.content[:child_refer].delete(refer_node)
134
+
135
+ # replace parent content
136
+ new_content = replace_root_child_code(root_node.content[:content], refer_node, child_type.type_param_content)
137
+ root_node.content[:content] = new_content
138
+ build_param_tree(parser, root_node.name, root_node, tree_root_node)
139
+ else
140
+ child_node = Tree::TreeNode.new(refer_node, { 'total_child': child_refer_nodes.size,
141
+ 'child_refer': child_refer_nodes,
142
+ 'self_refer': false,
143
+ 'prototype': child_type.type_param_content,
144
+ 'content': child_type.type_param_content })
145
+ root_node << child_node
146
+ tree_root_node.content[:all_child] = (tree_root_node.content[:all_child] + child_refer_nodes).uniq
147
+
148
+ # add refer_path
149
+ tree_root_node.content[:refer_path] << refer_node
150
+ build_param_tree(parser, refer_node, child_node, tree_root_node)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # update root_node ic types
157
+ if root_node.content[:ic_type].nil?
158
+ child_key_types = {}
159
+ root_node.children.each do |child_node|
160
+ child_key_types[child_node.name] = child_node.content[:ic_type]
161
+ end
162
+ ic_type = IcAgent::Ast::Assembler.build_type(root_node.content[:prototype], child_key_types)
163
+ root_node.content[:ic_type] = ic_type
164
+ end
165
+
166
+ root_node
167
+ end
168
+
169
+ # params array recursively call, traverse, and replace
170
+ def service_params_replace(arr, search_value, replace_value)
171
+ arr.map! do |element|
172
+ if element.is_a?(Array)
173
+ service_params_replace(element, search_value, replace_value)
174
+ else
175
+ element == search_value ? replace_value : element
176
+ end
177
+ end
178
+ end
179
+
180
+ def refer_type(param)
181
+ param_arr = param.strip.split(' ')
182
+ refer_type = param_arr - IcAgent::Candid::ALL_TYPES
183
+ refer_type.empty? ? nil : refer_type[0]
184
+ end
185
+
186
+ def only_refer_type?(param)
187
+ refer_types = IcAgent::Ast::Assembler.get_params_refer_values(param)
188
+ param.index(' ').nil? && refer_types.size == 1
189
+ end
190
+
191
+ def get_param_to_ic_type(parser, param)
192
+ ic_refer_types = {}
193
+ refer_types = IcAgent::Ast::Assembler.get_params_refer_values(param)
194
+ refer_types.each do |refer_code|
195
+ ic_refer_types[refer_code] = build_param_tree(parser, refer_code, nil, nil).content[:ic_type]
196
+ end
197
+
198
+ ic_type = nil
199
+ if param.index(' ') || refer_types.size > 1
200
+ ic_type = IcAgent::Ast::Assembler.build_type(param, ic_refer_types)
201
+ elsif ic_refer_types.keys.size == 1
202
+ ic_type = ic_refer_types.values[0]
203
+ end
204
+ ic_type
205
+ end
206
+
207
+ def decode_type_arg(parser, arg)
208
+ base_arg = arg.strip
209
+ decode_arg = base_arg
210
+ unless base_arg.include?(' ') || IcAgent::Candid::ALL_TYPES.any? { |str| base_arg.include?(str) }
211
+ arg_type = parser.ic_type_by_name(base_arg)
212
+ decode_arg = arg_type.type_param_content
213
+ end
214
+ decode_arg
215
+ end
216
+
217
+ def replace_root_child_code(root_text, find_text, replace_text)
218
+ new_root_text = root_text
219
+ parts = new_root_text.split(";")
220
+
221
+ if parts.size > 0
222
+ parts.map do |part|
223
+ last_index = part.rindex(find_text)
224
+ e_last_index = part.rindex("#{find_text}}")
225
+ b_last_index = part.rindex("#{find_text} ")
226
+ part[last_index..(last_index + find_text.size - 1)] = replace_text if last_index && (e_last_index || b_last_index || (last_index + find_text.size) == part.size)
227
+ end
228
+ new_root_text = parts.join(";")
229
+ else
230
+ last_index = new_root_text.rindex(find_text)
231
+ e_last_index = new_root_text.rindex("#{find_text}}")
232
+ b_last_index = new_root_text.rindex("#{find_text} ")
233
+ new_root_text[last_index..(last_index + find_text.size - 1)] = replace_text if last_index && (e_last_index || b_last_index || (last_index + find_text.size) == part.size)
234
+ end
235
+ new_root_text
236
+ end
237
+
238
+ def add_caniter_method(method_name, type_args, args_types, rets_type, anno = nil)
239
+ self.class.class_eval do
240
+ define_method(method_name) do |*args|
241
+ init_method_name = method_name
242
+ init_method_args = type_args.nil? ? [] : type_args.split(',').map(&:strip)
243
+ init_method_anno = anno
244
+ init_method_types = args_types
245
+ init_method_ret_type = rets_type
246
+
247
+ if init_method_args.length != args.length
248
+ raise ArgumentError, 'Arguments length not match'
249
+ end
250
+
251
+ arguments = []
252
+ args.each_with_index do |arg, i|
253
+ arguments << { 'type': init_method_types[i], 'value': arg }
254
+ end
255
+
256
+ effective_canister_id = @canister_id == 'aaaaa-aa' && init_method_args.length > 0 && init_method_args[0].is_a?(Hash) && init_method_args[0].key?('canister_id') ? init_method_args[0]['canister_id'] : @canister_id
257
+ res = if init_method_anno == 'query'
258
+ @agent.query_raw(@canister_id, init_method_name, IcAgent::Candid.encode(arguments), init_method_ret_type, effective_canister_id)
259
+ else
260
+ @agent.update_raw(@canister_id, init_method_name, IcAgent::Candid.encode(arguments), init_method_ret_type, effective_canister_id)
261
+ end
262
+ return res unless res.is_a?(Array)
263
+
264
+ res.map { |item| item['value'] }
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+
@@ -0,0 +1,55 @@
1
+ module IcAgent
2
+ class NodeId
3
+ EMPTY = 0
4
+ FORK = 1
5
+ LABELED = 2
6
+ LEAF = 3
7
+ PRUNED = 4
8
+ end
9
+
10
+ class Certificate
11
+ def self.lookup(path, cert)
12
+ lookup_path(path, cert.value['tree'])
13
+ end
14
+
15
+ def self.lookup_path(path, tree)
16
+ offset = 0
17
+ if path.length == 0
18
+ if tree[0] == NodeId::LEAF
19
+ return tree[1]
20
+ else
21
+ return nil
22
+ end
23
+ end
24
+ label = path[0].is_a?(String) ? path[0].encode : path[0]
25
+ t = find_label(label, flatten_forks(tree))
26
+ if t
27
+ offset += 1
28
+ lookup_path(path[offset..-1], t)
29
+ end
30
+ end
31
+
32
+ def self.flatten_forks(t)
33
+ if t[0] == NodeId::EMPTY
34
+ []
35
+ elsif t[0] == NodeId::FORK
36
+ val1 = flatten_forks(t[1])
37
+ val2 = flatten_forks(t[2])
38
+ val1.concat(val2)
39
+ val1
40
+ else
41
+ [t]
42
+ end
43
+ end
44
+
45
+ def self.find_label(l, trees)
46
+ trees.each do |t|
47
+ if t[0] == NodeId::LABELED
48
+ p = t[1]
49
+ return t[2] if l == p
50
+ end
51
+ end
52
+ nil
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ require 'faraday'
2
+
3
+ module IcAgent
4
+ class Client
5
+ DEFAULT_TIMEOUT = 120
6
+ DEFAULT_TIMEOUT_QUERY = 30
7
+
8
+ def initialize(url = 'https://ic0.app')
9
+ @url = url
10
+ @conn = Faraday.new(url: url) do |faraday|
11
+ faraday.request :url_encoded
12
+ faraday.adapter Faraday.default_adapter
13
+ faraday.headers['Content-Type'] = 'application/cbor'
14
+ faraday.options.timeout = DEFAULT_TIMEOUT
15
+ faraday.options.open_timeout = DEFAULT_TIMEOUT_QUERY
16
+ end
17
+ end
18
+
19
+ def query(canister_id, data)
20
+ endpoint = "/api/v2/canister/#{canister_id}/query"
21
+ ret = @conn.post(endpoint, data)
22
+ ret.body.force_encoding('ISO-8859-1').encode('UTF-8')
23
+ ret.body
24
+ end
25
+
26
+ def call(canister_id, req_id, data)
27
+ endpoint = "/api/v2/canister/#{canister_id}/call"
28
+ ret = @conn.post(endpoint, data)
29
+ ret.body.force_encoding('ISO-8859-1').encode('UTF-8')
30
+ req_id
31
+ end
32
+
33
+ def read_state(canister_id, data)
34
+ endpoint = "/api/v2/canister/#{canister_id}/read_state"
35
+ ret = @conn.post(endpoint, data)
36
+ ret.body.force_encoding('ISO-8859-1').encode('UTF-8')
37
+ ret.body
38
+ end
39
+
40
+ def status(timeout: DEFAULT_TIMEOUT_QUERY)
41
+ endpoint = '/api/v2/status'
42
+ ret = @conn.get(endpoint, timeout: timeout)
43
+ puts "client.status: #{ret.body.force_encoding('ISO-8859-1').encode('UTF-8')}"
44
+ ret.body
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,275 @@
1
+ module IcAgent
2
+ module Common
3
+ class CyclesWallet
4
+ DID_FILE = <<~DIDL_DOC
5
+ type EventKind = variant {
6
+ CyclesSent: record {
7
+ to: principal;
8
+ amount: nat64;
9
+ refund: nat64;
10
+ };
11
+ CyclesReceived: record {
12
+ from: principal;
13
+ amount: nat64;
14
+ memo: opt text;
15
+ };
16
+ AddressAdded: record {
17
+ id: principal;
18
+ name: opt text;
19
+ role: Role;
20
+ };
21
+ AddressRemoved: record {
22
+ id: principal;
23
+ };
24
+ CanisterCreated: record {
25
+ canister: principal;
26
+ cycles: nat64;
27
+ };
28
+ CanisterCalled: record {
29
+ canister: principal;
30
+ method_name: text;
31
+ cycles: nat64;
32
+ };
33
+ WalletDeployed: record {
34
+ canister: principal;
35
+ }
36
+ };
37
+
38
+ type EventKind128 = variant {
39
+ CyclesSent: record {
40
+ to: principal;
41
+ amount: nat;
42
+ refund: nat;
43
+ };
44
+ CyclesReceived: record {
45
+ from: principal;
46
+ amount: nat;
47
+ memo: opt text;
48
+ };
49
+ AddressAdded: record {
50
+ id: principal;
51
+ name: opt text;
52
+ role: Role;
53
+ };
54
+ AddressRemoved: record {
55
+ id: principal;
56
+ };
57
+ CanisterCreated: record {
58
+ canister: principal;
59
+ cycles: nat;
60
+ };
61
+ CanisterCalled: record {
62
+ canister: principal;
63
+ method_name: text;
64
+ cycles: nat;
65
+ };
66
+ WalletDeployed: record {
67
+ canister: principal;
68
+ };
69
+ };
70
+
71
+ type Event = record {
72
+ id: nat32;
73
+ timestamp: nat64;
74
+ kind: EventKind;
75
+ };
76
+
77
+ type Event128 = record {
78
+ id: nat32;
79
+ timestamp: nat64;
80
+ kind: EventKind128;
81
+ };
82
+
83
+ type Role = variant {
84
+ Contact;
85
+ Custodian;
86
+ Controller;
87
+ };
88
+
89
+ type Kind = variant {
90
+ Unknown;
91
+ User;
92
+ Canister;
93
+ };
94
+
95
+ // An entry in the address book. It must have an ID and a role.
96
+ type AddressEntry = record {
97
+ id: principal;
98
+ name: opt text;
99
+ kind: Kind;
100
+ role: Role;
101
+ };
102
+
103
+ type ManagedCanisterInfo = record {
104
+ id: principal;
105
+ name: opt text;
106
+ created_at: nat64;
107
+ };
108
+
109
+ type ManagedCanisterEventKind = variant {
110
+ CyclesSent: record {
111
+ amount: nat64;
112
+ refund: nat64;
113
+ };
114
+ Called: record {
115
+ method_name: text;
116
+ cycles: nat64;
117
+ };
118
+ Created: record {
119
+ cycles: nat64;
120
+ };
121
+ };
122
+
123
+ type ManagedCanisterEventKind128 = variant {
124
+ CyclesSent: record {
125
+ amount: nat;
126
+ refund: nat;
127
+ };
128
+ Called: record {
129
+ method_name: text;
130
+ cycles: nat;
131
+ };
132
+ Created: record {
133
+ cycles: nat;
134
+ };
135
+ };
136
+
137
+ type ManagedCanisterEvent = record {
138
+ id: nat32;
139
+ timestamp: nat64;
140
+ kind: ManagedCanisterEventKind;
141
+ };
142
+
143
+ type ManagedCanisterEvent128 = record {
144
+ id: nat32;
145
+ timestamp: nat64;
146
+ kind: ManagedCanisterEventKind128;
147
+ };
148
+
149
+ type ReceiveOptions = record {
150
+ memo: opt text;
151
+ };
152
+
153
+ type WalletResultCreate = variant {
154
+ Ok : record { canister_id: principal };
155
+ Err: text;
156
+ };
157
+
158
+ type WalletResult = variant {
159
+ Ok : null;
160
+ Err : text;
161
+ };
162
+
163
+ type WalletResultCall = variant {
164
+ Ok : record { return: blob };
165
+ Err : text;
166
+ };
167
+
168
+ type CanisterSettings = record {
169
+ controller: opt principal;
170
+ controllers: opt vec principal;
171
+ compute_allocation: opt nat;
172
+ memory_allocation: opt nat;
173
+ freezing_threshold: opt nat;
174
+ };
175
+
176
+ type CreateCanisterArgs = record {
177
+ cycles: nat64;
178
+ settings: CanisterSettings;
179
+ };
180
+
181
+ type CreateCanisterArgs128 = record {
182
+ cycles: nat;
183
+ settings: CanisterSettings;
184
+ };
185
+
186
+ // Assets
187
+ type HeaderField = record { text; text; };
188
+
189
+ type HttpRequest = record {
190
+ method: text;
191
+ url: text;
192
+ headers: vec HeaderField;
193
+ body: blob;
194
+ };
195
+
196
+ type HttpResponse = record {
197
+ status_code: nat16;
198
+ headers: vec HeaderField;
199
+ body: blob;
200
+ streaming_strategy: opt StreamingStrategy;
201
+ };
202
+
203
+ type StreamingCallbackHttpResponse = record {
204
+ body: blob;
205
+ token: opt Token;
206
+ };
207
+
208
+ type Token = record {};
209
+
210
+ type StreamingStrategy = variant {
211
+ Callback: record {
212
+ callback: func (Token) -> (StreamingCallbackHttpResponse) query;
213
+ token: Token;
214
+ };
215
+ };
216
+
217
+ service : {
218
+ wallet_api_version: () -> (text) query;
219
+ name: () -> (opt text) query;
220
+ set_name: (text) -> ();
221
+ get_controllers: () -> (vec principal) query;
222
+ add_controller: (principal) -> ();
223
+ remove_controller: (principal) -> (WalletResult);
224
+ get_custodians: () -> (vec principal) query;
225
+ authorize: (principal) -> ();
226
+ deauthorize: (principal) -> (WalletResult);
227
+ wallet_balance: () -> (record { amount: nat64 }) query;
228
+ wallet_balance128: () -> (record { amount: nat }) query;
229
+ wallet_send: (record { canister: principal; amount: nat64 }) -> (WalletResult);
230
+ wallet_send128: (record { canister: principal; amount: nat }) -> (WalletResult);
231
+ wallet_receive: (opt ReceiveOptions) -> (); // Endpoint for receiving cycles.
232
+ wallet_create_canister: (CreateCanisterArgs) -> (WalletResultCreate);
233
+ wallet_create_canister128: (CreateCanisterArgs128) -> (WalletResultCreate);
234
+ wallet_create_wallet: (CreateCanisterArgs) -> (WalletResultCreate);
235
+ wallet_create_wallet128: (CreateCanisterArgs128) -> (WalletResultCreate);
236
+ wallet_store_wallet_wasm: (record {
237
+ wasm_module: blob;
238
+ }) -> ();
239
+ wallet_call: (record {
240
+ canister: principal;
241
+ method_name: text;
242
+ args: blob;
243
+ cycles: nat64;
244
+ }) -> (WalletResultCall);
245
+ wallet_call128: (record {
246
+ canister: principal;
247
+ method_name: text;
248
+ args: blob;
249
+ cycles: nat;
250
+ }) -> (WalletResultCall);
251
+ add_address: (address: AddressEntry) -> ();
252
+ list_addresses: () -> (vec AddressEntry) query;
253
+ remove_address: (address: principal) -> (WalletResult);
254
+ get_events: (opt record { from: opt nat32; to: opt nat32; }) -> (vec Event) query;
255
+ get_events128: (opt record { from: opt nat32; to: opt nat32; }) -> (vec Event128) query;
256
+ get_chart: (opt record { count: opt nat32; precision: opt nat64; } ) -> (vec record { nat64; nat64; }) query;
257
+ list_managed_canisters: (record { from: opt nat32; to: opt nat32; }) -> (vec ManagedCanisterInfo, nat32) query;
258
+ get_managed_canister_events: (record { canister: principal; from: opt nat32; to: opt nat32; }) -> (opt vec ManagedCanisterEvent) query;
259
+ get_managed_canister_events128: (record { canister: principal; from: opt nat32; to: opt nat32; }) -> (opt vec ManagedCanisterEvent128) query;
260
+ set_short_name: (principal, opt text) -> (opt ManagedCanisterInfo);
261
+ http_request: (request: HttpRequest) -> (HttpResponse) query;
262
+ }
263
+ DIDL_DOC
264
+
265
+ attr_accessor :identity, :client, :agent, :canister
266
+
267
+ def initialize(iden = nil, wallet_id)
268
+ @identity = iden.nil? ? IcAgent::Identity.new : iden
269
+ @client = IcAgent::Client.new
270
+ @agent = IcAgent::Agent.new(@identity, @client)
271
+ @canister = IcAgent::Canister.new(@agent, wallet_id, DID_FILE)
272
+ end
273
+ end
274
+ end
275
+ end