suidouble 0.0.16 → 0.0.18

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/README.md CHANGED
@@ -9,6 +9,7 @@ Set of provider, package and object classes for javascript representation of Sui
9
9
  - [Interacting with smart contract](#interacting-with-smart-contract)
10
10
  - [SuiObject](#suiobject)
11
11
  - [Fetching Events](#fetching-events)
12
+ - [Subscribe to Events](#subscribing-to-events)
12
13
  - [Executing smart contract method](#executing-smart-contract-method)
13
14
  - [Fetching objects](#fetching-objects)
14
15
  - [Publishing the package](#publishing-the-package)
@@ -138,6 +139,35 @@ while (events.hasNextPage) {
138
139
  // const events = await contract.fetchEvents('modulename', {order: 'descending'}); // or all module events
139
140
  ```
140
141
 
142
+ ##### subscribing to events
143
+
144
+ You can subscribe to Sui's contract events on package's module level. No types-etc filters for now ( @todo? )
145
+
146
+ ```javascript
147
+ const module = await contract.getModule('suidouble_chat');
148
+ await module.subscribeEvents();
149
+ module.addEventListener('ChatResponseCreated', (suiEvent)=>{
150
+ // received message emited by
151
+ // emit(ChatResponseCreated { id: object::uid_to_inner(&chat_response_id), top_message_id: object::uid_to_inner(&id), seq_n: 0 });
152
+ // in suidouble_chat 's smart contract
153
+ console.log(suiEvent.typeName); // == 'ChatResponseCreated'
154
+ console.log(suiEvent.parsedJson);
155
+ });
156
+ module.addEventListener('ChatTopMessageCreated', (suiEvent)=>{
157
+ // received message emited by
158
+ // emit(ChatTopMessageCreated { id: object::uid_to_inner(&id), top_response_id: object::uid_to_inner(&chat_response_id), });
159
+ // in suidouble_chat 's smart contract
160
+ console.log(suiEvent.typeName); // == 'ChatTopMessageCreated'
161
+ console.log(suiEvent.parsedJson);
162
+ });
163
+ ```
164
+
165
+ Don't forget to unsubscribe from events when you don't need them anymore:
166
+
167
+ ```javascript
168
+ await module.unsubscribeEvents();
169
+ ```
170
+
141
171
  ##### executing smart contract method
142
172
 
143
173
  ```javascript
@@ -305,6 +335,15 @@ suiInBrowser.addEventListener('connected', async()=>{
305
335
 
306
336
  ```
307
337
 
338
+ ### Unit tests
339
+
340
+ ```bash
341
+ npm install
342
+ npm run tests
343
+ ```
344
+
345
+ Take a look at [unit tests](test) code for some inspiration.
346
+
308
347
  ### Todo
309
348
 
310
349
  - subscribe to events
@@ -34,7 +34,11 @@ class SuiCommonMethods extends EventTarget {
34
34
 
35
35
  emit(eventType, data) {
36
36
  try {
37
- this.dispatchEvent(new CustomEvent(eventType, { detail: data }));
37
+ if (data.isSuiEvent) {
38
+ this.dispatchEvent(data);
39
+ } else {
40
+ this.dispatchEvent(new CustomEvent(eventType, { detail: data }));
41
+ }
38
42
  } catch (e) {
39
43
  console.error(e);
40
44
  }
package/lib/SuiEvent.js CHANGED
@@ -1,15 +1,36 @@
1
1
  const SuiCommonMethods = require('./SuiCommonMethods.js');
2
2
 
3
- class SuiEvent extends SuiCommonMethods {
3
+ class SuiEvent extends Event {
4
4
  constructor(params = {}) {
5
- super(params);
5
+ const typeName = params.data ? (params.data.type.split('::').pop()) : null;
6
+ super(typeName, {});
6
7
 
8
+ this._debug = !!params.debug;
7
9
  this._suiMaster = params.suiMaster;
8
10
  if (!this._suiMaster) {
9
11
  throw new Error('suiMaster is requried for suiPackage');
10
12
  }
11
13
 
12
14
  this._data = params.data || {};
15
+
16
+ this.detail = this; // quick backward support as this is the instance of CustomEvent
17
+ }
18
+
19
+ log(...args) {
20
+ if (!this._debug) {
21
+ return;
22
+ }
23
+
24
+ let prefix = (this._suiMaster ? (''+this._suiMaster.instanceN+' |') : (this.instanceN ? ''+this.instanceN+' |' : '') );
25
+ // prefix += this.constructor.name+' | ';
26
+
27
+ args.unshift(this.constructor.name+' |');
28
+ args.unshift(prefix);
29
+ console.info.apply(null, args);
30
+ }
31
+
32
+ get isSuiEvent() {
33
+ return true;
13
34
  }
14
35
 
15
36
  /**
@@ -105,7 +105,6 @@ class SuiLocalTestValidator extends SuiCommonMethods {
105
105
  });
106
106
 
107
107
  process.on('exit', ()=>{
108
- console.log('this._testFallbackEnabled', this._testFallbackEnabled);
109
108
  if (this._child) {
110
109
  this._child.kill();
111
110
  }
package/lib/SuiMaster.js CHANGED
@@ -15,8 +15,13 @@ class SuiMaster extends SuiCommonMethods {
15
15
  this._signer = null;
16
16
  this._keypair = null;
17
17
 
18
+ this._address = null;
19
+
18
20
  if (params.signer) {
19
21
  this._signer = params.signer;
22
+ if (this._signer && this._signer.connectedAddress) {
23
+ this._address = this._signer.connectedAddress;
24
+ }
20
25
  } else if (params.keypair) {
21
26
  this._keypair = params.keypair;
22
27
  } else if (params.phrase) {
@@ -86,8 +91,6 @@ class SuiMaster extends SuiCommonMethods {
86
91
 
87
92
  this._initialized = false;
88
93
 
89
- this._address = null;
90
-
91
94
  this._packages = {};
92
95
  }
93
96
 
@@ -3,6 +3,7 @@ const SuiObject = require('./SuiObject.js');
3
3
 
4
4
  const SuiCommonMethods = require('./SuiCommonMethods.js');
5
5
  const SuiPaginatedResponse = require('./SuiPaginatedResponse.js');
6
+ const SuiEvent = require('./SuiEvent.js');
6
7
  // fromB64, toB64
7
8
 
8
9
  class SuiPackageModule extends SuiCommonMethods {
@@ -28,6 +29,43 @@ class SuiPackageModule extends SuiCommonMethods {
28
29
  // we need to get very first version's address of this package to use for types, so we are doing this in separate call
29
30
  this._checkedOnChain = false;
30
31
  this._normalizedMoveModule = {};
32
+
33
+ this._unsubscribeFunction = null;
34
+ }
35
+
36
+ async subscribeEvents() {
37
+ this.log('subscribing to events of module', this._moduleName);
38
+
39
+ // we need very first package version's id here. So we are getting it from normalized data
40
+ const normalizedPackageAddress = await this.getNormalizedPackageAddress();
41
+
42
+ const onMessage = (rawEvent) => {
43
+ const suiEvent = new SuiEvent({
44
+ suiMaster: this._suiMaster,
45
+ debug: this._debug,
46
+ data: rawEvent,
47
+ });
48
+
49
+ const eventTypeName = suiEvent.typeName;
50
+ this.log('got event', eventTypeName);
51
+ this.emit(eventTypeName, suiEvent);
52
+ };
53
+
54
+ this._unsubscribeFunction = await this._suiMaster._provider.subscribeEvent({
55
+ filter: {"MoveModule": {"package": normalizedPackageAddress, "module": this._moduleName} },
56
+ onMessage: onMessage,
57
+ });
58
+ }
59
+
60
+ async unsubscribeEvents() {
61
+ if (this._unsubscribeFunction) {
62
+ await this._unsubscribeFunction();
63
+ this._unsubscribeFunction = null;
64
+
65
+ return true;
66
+ }
67
+
68
+ return false;
31
69
  }
32
70
 
33
71
  get objectStorage() {
@@ -181,8 +219,15 @@ class SuiPackageModule extends SuiCommonMethods {
181
219
  const eventType = event.type;
182
220
  const eventTypeName = eventType.split(':').pop(); // last name, without package and module names
183
221
 
184
- const eventData = event.parsedJson;
185
- this.emit(eventTypeName, eventData);
222
+ // const eventData = event.parsedJson;
223
+
224
+ const suiEvent = new SuiEvent({
225
+ suiMaster: this._suiMaster,
226
+ debug: this._debug,
227
+ data: event,
228
+ });
229
+
230
+ this.emit(eventTypeName, suiEvent);
186
231
  }
187
232
  }
188
233
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suidouble",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Set of provider, package and object classes for javascript representation of Sui Move smart contracts. Use same code for publishing, upgrading, integration testing, interaction with smart contracts and integration in browser web3 dapps",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  "author": "Jeka Kiselyov <jeka911@gmail.com> (https://github.com/jeka-kiselyov)",
22
22
  "license": "Apache-2.0",
23
23
  "dependencies": {
24
- "@mysten/sui.js": "^0.34.0",
24
+ "@mysten/sui.js": "^0.35.1",
25
25
  "@wallet-standard/core": "^1.0.3"
26
26
  },
27
27
  "devDependencies": {
@@ -25,7 +25,7 @@ test('spawn local test node', async t => {
25
25
  });
26
26
 
27
27
  test('init suiMaster and connect it to local test validator', async t => {
28
- suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody', debug: false});
28
+ suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody', debug: true});
29
29
  await suiMaster.initialize();
30
30
 
31
31
  t.ok(suiMaster.address); // there should be some address
@@ -150,6 +150,34 @@ test('can find a package on the blockchain by expected module name (in owned)',
150
150
  t.equal(contract.version, 2);
151
151
  });
152
152
 
153
+ test('subscribe to module events', async t => {
154
+ const module = await contract.getModule('suidouble_chat');
155
+ await module.subscribeEvents();
156
+
157
+ let gotEventChatTopMessageCreated = false;
158
+ let gotEventChatResponseCreated = false;
159
+
160
+ module.addEventListener('ChatTopMessageCreated', (event)=>{
161
+ gotEventChatTopMessageCreated = event;
162
+ });
163
+ module.addEventListener('ChatResponseCreated', (event)=>{
164
+ gotEventChatResponseCreated = event.detail; // .detail is reference to event itself. To support CustomEvent pattern
165
+ });
166
+
167
+ await contract.moveCall('suidouble_chat', 'post', [chatShopObjectId, 'the message', 'metadata']);
168
+ await new Promise((res)=>setTimeout(res, 300)); // got events without timeout, but just to be sure.
169
+
170
+ t.ok(gotEventChatTopMessageCreated);
171
+ t.ok(gotEventChatResponseCreated);
172
+
173
+ // just some checks that events have data by contract's architecture
174
+ t.ok(gotEventChatResponseCreated.parsedJson.top_message_id == gotEventChatTopMessageCreated.parsedJson.id);
175
+ t.ok(gotEventChatTopMessageCreated.parsedJson.top_response_id == gotEventChatResponseCreated.parsedJson.id);
176
+
177
+ // unsubscribing from events, to close websocket
178
+ await module.unsubscribeEvents();
179
+ });
180
+
153
181
  test('execute contract methods', async t => {
154
182
  const moveCallResult = await contract.moveCall('suidouble_chat', 'post', [chatShopObjectId, 'the message', 'metadata']);
155
183
 
@@ -290,5 +318,5 @@ test('testing move call with coins', async t => {
290
318
  });
291
319
 
292
320
  test('stops local test node', async t => {
293
- SuiLocalTestValidator.stop();
321
+ await SuiLocalTestValidator.stop();
294
322
  });
@@ -0,0 +1,20 @@
1
+ # @generated by Move, please check-in and do not edit manually.
2
+
3
+ [move]
4
+ version = 0
5
+
6
+ dependencies = [
7
+ { name = "Sui" },
8
+ ]
9
+
10
+ [[move.package]]
11
+ name = "MoveStdlib"
12
+ source = { git = "https://github.com/MystenLabs/sui.git", rev = "testnet", subdir = "crates/sui-framework/packages/move-stdlib" }
13
+
14
+ [[move.package]]
15
+ name = "Sui"
16
+ source = { git = "https://github.com/MystenLabs/sui.git", rev = "testnet", subdir = "crates/sui-framework/packages/sui-framework" }
17
+
18
+ dependencies = [
19
+ { name = "MoveStdlib" },
20
+ ]
@@ -0,0 +1,10 @@
1
+ [package]
2
+ name = "suidouble_color"
3
+ version = "0.0.1"
4
+
5
+ [dependencies]
6
+ Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "testnet" }
7
+
8
+ [addresses]
9
+ suidouble_color = "0x0"
10
+ sui = "0000000000000000000000000000000000000000000000000000000000000002"
@@ -0,0 +1,150 @@
1
+
2
+ module suidouble_color::suidouble_color {
3
+ use sui::tx_context::{Self, sender, TxContext};
4
+ use std::string::{Self, utf8, String};
5
+ use sui::transfer;
6
+ use sui::object::{Self, UID, ID};
7
+ use std::vector::{Self, append, insert};
8
+
9
+ use sui::event::emit;
10
+
11
+ // The creator bundle: these two packages often go together.
12
+ use sui::package;
13
+ use sui::display;
14
+
15
+ /// Text size overflow.
16
+ const EInvalidColor: u64 = 0;
17
+
18
+ // ======== Events =========
19
+
20
+ /// Event. When a new color minted
21
+ struct ColorCreated has copy, drop { id: ID, r: u8, g: u8, b: u8 }
22
+
23
+ /// The Hero - an outstanding collection of digital art.
24
+ struct Color has key, store {
25
+ id: UID,
26
+ name: String,
27
+ r: u8,
28
+ g: u8,
29
+ b: u8,
30
+ img_url: String,
31
+ }
32
+
33
+ /// One-Time-Witness for the module.
34
+ struct SUIDOUBLE_COLOR has drop {}
35
+
36
+ /// In the module initializer we claim the `Publisher` object
37
+ /// to then create a `Display`. The `Display` is initialized with
38
+ /// a set of fields (but can be modified later) and published via
39
+ /// the `update_version` call.
40
+ ///
41
+ /// Keys and values are set in the initializer but could also be
42
+ /// set after publishing if a `Publisher` object was created.
43
+ fun init(otw: SUIDOUBLE_COLOR, ctx: &mut TxContext) {
44
+ let keys = vector[
45
+ utf8(b"name"),
46
+ utf8(b"link"),
47
+ utf8(b"image_url"),
48
+ utf8(b"description"),
49
+ utf8(b"project_url"),
50
+ utf8(b"creator"),
51
+ ];
52
+
53
+ let values = vector[
54
+ utf8(b"{name}"),
55
+ // For `link` we can build a URL using an `id` property
56
+ utf8(b"https://suidouble-color.herokuapp.com/color/{id}"),
57
+ utf8(b"{img_url}"),
58
+ // Description is static for all `Color` objects.
59
+ utf8(b"What a nice color. Isn't it?"),
60
+ // Project URL is usually static
61
+ utf8(b"https://suidouble-color.herokuapp.com/"),
62
+ // Creator field can be any
63
+ utf8(b"Jeka")
64
+ ];
65
+
66
+ // Claim the `Publisher` for the package!
67
+ let publisher = package::claim(otw, ctx);
68
+
69
+ // Get a new `Display` object for the `Color` type.
70
+ let display = display::new_with_fields<Color>(
71
+ &publisher, keys, values, ctx
72
+ );
73
+
74
+ // Commit first version of `Display` to apply changes.
75
+ display::update_version(&mut display);
76
+
77
+ transfer::public_transfer(publisher, sender(ctx));
78
+ transfer::public_transfer(display, sender(ctx));
79
+ }
80
+
81
+ /// Anyone can mint their `Color`!
82
+ public entry fun mint(name: String, r: u8, g: u8, b: u8, ctx: &mut TxContext) {
83
+ assert!(r >= 0 && r <= 255, EInvalidColor);
84
+ assert!(g >= 0 && g <= 255, EInvalidColor);
85
+ assert!(b >= 0 && b <= 255, EInvalidColor);
86
+
87
+ let id = object::new(ctx);
88
+
89
+ emit(ColorCreated { id: object::uid_to_inner(&id), r, g, b, });
90
+
91
+ // constructing the smallest (1x1) GIF with the color of RGB
92
+ let gif_start = vector<u8>[71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 1, 0];
93
+ let gif_end = vector<u8>[0, 0, 0, 33, 249, 4, 1, 10, 0, 1, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59];
94
+
95
+ insert(&mut gif_start, r, 13); // appending R
96
+ insert(&mut gif_start, g, 14); // appending G
97
+ insert(&mut gif_start, b, 15); // appending B
98
+ append(&mut gif_start, gif_end);
99
+
100
+ let img_url = encode(&gif_start);
101
+
102
+ let base_prefix = b"data:image/gif;base64,";
103
+ let as_string = utf8(base_prefix);
104
+ string::append(&mut as_string, utf8(img_url));
105
+
106
+ //
107
+ let color = Color { id, name, img_url: as_string, r: r, g: g, b: b };
108
+
109
+ transfer::transfer(color, tx_context::sender(ctx));
110
+ }
111
+
112
+ // thanks to: https://github.com/movefuns/movefuns/blob/dd1f4443c6bf0bc761b27e28fb6ba00f10636840/stdlib/sources/base64.move#L2
113
+ const TABLE: vector<u8> = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
114
+
115
+ public fun encode(str: &vector<u8>): vector<u8> {
116
+ if (vector::is_empty(str)) {
117
+ return vector::empty<u8>()
118
+ };
119
+ let size = vector::length(str);
120
+ let eq: u8 = 61;
121
+ let res = vector::empty<u8>();
122
+
123
+ let m = 0 ;
124
+ while (m < size ) {
125
+ vector::push_back(&mut res, *vector::borrow(&TABLE, (((*vector::borrow(str, m) & 0xfc) >> 2) as u64)));
126
+ if ( m + 3 >= size) {
127
+ if ( size % 3 == 1) {
128
+ vector::push_back(&mut res, *vector::borrow(&TABLE, (((*vector::borrow(str, m) & 0x03) << 4) as u64)));
129
+ vector::push_back(&mut res, eq);
130
+ vector::push_back(&mut res, eq);
131
+ }else if (size % 3 == 2) {
132
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((((*vector::borrow(str, m) & 0x03) << 4) + ((*vector::borrow(str, m + 1) & 0xf0) >> 4)) as u64)));
133
+ vector::push_back(&mut res, *vector::borrow(&TABLE, (((*vector::borrow(str, m + 1) & 0x0f) << 2) as u64)));
134
+ vector::push_back(&mut res, eq);
135
+ }else {
136
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((((*vector::borrow(str, m) & 0x03) << 4) + ((*vector::borrow(str, m + 1) & 0xf0) >> 4)) as u64)));
137
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((((*vector::borrow(str, m + 1) & 0x0f) << 2) + ((*vector::borrow(str, m + 2) & 0xc0) >> 6)) as u64)));
138
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((*vector::borrow(str, m + 2) & 0x3f) as u64)));
139
+ };
140
+ }else {
141
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((((*vector::borrow(str, m) & 0x03) << 4) + ((*vector::borrow(str, m + 1) & 0xf0) >> 4)) as u64)));
142
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((((*vector::borrow(str, m + 1) & 0x0f) << 2) + ((*vector::borrow(str, m + 2) & 0xc0) >> 6)) as u64)));
143
+ vector::push_back(&mut res, *vector::borrow(&TABLE, ((*vector::borrow(str, m + 2) & 0x3f) as u64)));
144
+ };
145
+ m = m + 3;
146
+ };
147
+
148
+ return res
149
+ }
150
+ }