suidouble 0.0.6 → 0.0.9

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/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  const SuiMaster = require('./lib/SuiMaster.js');
2
2
  const SuiInBrowser = require('./lib/SuiInBrowser.js');
3
3
  const SuiTestScenario = require('./lib/SuiTestScenario.js');
4
+ const SuiLocalTestValidator = require('./lib/SuiLocalTestValidator.js');
4
5
 
5
6
  module.exports = {
6
7
  SuiMaster,
7
8
  SuiInBrowser,
8
9
  SuiTestScenario,
10
+ SuiLocalTestValidator,
9
11
  };
package/lib/SuiEvent.js CHANGED
@@ -12,6 +12,13 @@ class SuiEvent extends SuiCommonMethods {
12
12
  this._data = params.data || {};
13
13
  }
14
14
 
15
+ /**
16
+ * In module type name, without package and module prefix
17
+ */
18
+ get typeName() {
19
+ return this._data ? this._data.type.split('::').pop() : null;
20
+ }
21
+
15
22
  get data() {
16
23
  return this._data;
17
24
  }
@@ -10,6 +10,10 @@ class SuiLocalTestValidator extends SuiCommonMethods {
10
10
  this._active = false;
11
11
  }
12
12
 
13
+ get active() {
14
+ return this._active;
15
+ }
16
+
13
17
  static async launch(params = {}) {
14
18
  if (SuiLocalTestValidator.__instance) {
15
19
  return await SuiLocalTestValidator.__instance.launch();
@@ -27,19 +31,12 @@ class SuiLocalTestValidator extends SuiCommonMethods {
27
31
 
28
32
  async launch() {
29
33
  if (this._child && this._active) {
30
- return true;
34
+ return this;
31
35
  }
32
36
 
33
37
  this.log('launching sui-test-validator ...');
34
38
 
35
39
  this._child = await SuiCliCommands.spawn('sui-test-validator', { RUST_LOG: 'consensus=off' });
36
-
37
- // spawn('sui-test-validator', [], {
38
- // env: {
39
- // ...process.env,
40
- // RUST_LOG: 'consensus=off',
41
- // }
42
- // });
43
40
 
44
41
  this.__readyLaunchedPromiseResolver = null;
45
42
  this.__readyLaunchedPromise = new Promise((res)=>{
package/lib/SuiMaster.js CHANGED
@@ -35,22 +35,34 @@ class SuiMaster extends SuiCommonMethods {
35
35
  if (params.provider) {
36
36
  if (params.provider == 'local' || (params.provider.constructor && params.provider.constructor.name && params.provider.constructor.name == 'SuiLocalTestValidator')) {
37
37
  this._provider = new sui.JsonRpcProvider(sui.localnetConnection);
38
- this._providerName = 'local';
38
+ this._providerName = 'sui:localnet';
39
39
  } else if (params.provider == 'test' || params.provider == 'testnet') {
40
40
  this._provider = new sui.JsonRpcProvider(sui.testnetConnection);
41
- this._providerName = 'test';
41
+ this._providerName = 'sui:testnet';
42
42
  } else if (params.provider == 'dev' || params.provider == 'devnet') {
43
43
  this._provider = new sui.JsonRpcProvider(sui.devnetConnection);
44
- this._providerName = 'dev';
44
+ this._providerName = 'sui:devnet';
45
45
  } else if (params.provider == 'main' || params.provider == 'mainnet') {
46
46
  this._provider = new sui.JsonRpcProvider(sui.mainnetConnection);
47
- this._providerName = 'main';
47
+ this._providerName = 'sui:mainnet';
48
48
 
49
49
  this.log('we are on the mainnet, working with real money, be careful');
50
50
  } else {
51
51
  if (params.provider && params.provider.connection && params.provider.connection.fullnode) {
52
52
  this._provider = params.provider;
53
- this._providerName = params.provider.connection.fullnode;
53
+
54
+ if (params.provider.connection.fullnode.indexOf('devnet') !== -1) {
55
+ this._providerName = 'sui:devnet';
56
+ } else if (params.provider.connection.fullnode.indexOf('testnet') !== -1) {
57
+ this._providerName = 'sui:testnet';
58
+ } else if (params.provider.connection.fullnode.indexOf('mainnet') !== -1) {
59
+ this._providerName = 'sui:mainnet';
60
+ } else if (params.provider.connection.fullnode.indexOf('127.0.0.1') !== -1) {
61
+ this._providerName = 'sui:localnet';
62
+ } else {
63
+ // just keep provider name as unique to fullnode URL to keep separate ObjectStorage instances
64
+ this._providerName = params.provider.connection.fullnode;
65
+ }
54
66
  }
55
67
  }
56
68
  }
@@ -85,6 +97,10 @@ class SuiMaster extends SuiCommonMethods {
85
97
  return this._provider;
86
98
  }
87
99
 
100
+ get connectedChain() {
101
+ return this._providerName;
102
+ }
103
+
88
104
  get address() {
89
105
  return this._address;
90
106
  }
@@ -11,6 +11,12 @@ class SuiMemoryObjectStorage extends SuiCommonMethods {
11
11
  return Object.values(this._objects);
12
12
  }
13
13
 
14
+ findMostRecentByTypeName(typeName) {
15
+ return this.findMostRecent((object) => {
16
+ return (object.typeName == typeName);
17
+ });
18
+ }
19
+
14
20
  find(filterFunction) {
15
21
  for (const id in this._objects) {
16
22
  if (filterFunction(this._objects[id])) {
package/lib/SuiPackage.js CHANGED
@@ -43,6 +43,19 @@ class SuiPackage extends SuiObject {
43
43
  return this._modules;
44
44
  }
45
45
 
46
+ async getModule(moduleName) {
47
+ await this.checkOnChainIfNeeded();
48
+ return this._modules[moduleName];
49
+ }
50
+
51
+ get isBuilt() {
52
+ return this._isBuilt;
53
+ }
54
+
55
+ get version() {
56
+ return Number(this._publishedVersion); // return as Number in getter
57
+ }
58
+
46
59
  async isOnChain() {
47
60
  try {
48
61
  await this.checkOnChainIfNeeded();
@@ -223,9 +236,6 @@ class SuiPackage extends SuiObject {
223
236
  },
224
237
  });
225
238
 
226
- console.log('result', result);
227
-
228
-
229
239
  if (result?.data?.version) {
230
240
  this._publishedVersion = BigInt(result?.data?.version); // not sure, but it's string in response, so let's convert it to bigint, who knows
231
241
  this._isPublished = true;
@@ -104,8 +104,6 @@ class SuiPackageModule extends SuiCommonMethods {
104
104
  const listMutated = [];
105
105
  const listDeleted = [];
106
106
 
107
- console.error('result', result);
108
-
109
107
  for (const objectChange of result.objectChanges) {
110
108
  if (objectChange.objectId) {
111
109
  if (this.objectStorage.byAddress(objectChange.objectId)) {
package/package.json CHANGED
@@ -1,16 +1,36 @@
1
1
  {
2
2
  "name": "suidouble",
3
- "version": "0.0.6",
3
+ "version": "0.0.9",
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": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "test": "tap -j=1 ./test/*.test.js",
8
+ "coverage": "tap -j=1 ./test/*.test.js"
8
9
  },
9
- "keywords": ["sui", "sui move", "move", "smart contract", "smart contracts", "sui.js", "web3", "dapps", "dapp"],
10
+ "keywords": [
11
+ "sui",
12
+ "sui move",
13
+ "move",
14
+ "smart contract",
15
+ "smart contracts",
16
+ "sui.js",
17
+ "web3",
18
+ "dapps",
19
+ "dapp"
20
+ ],
10
21
  "author": "Jeka Kiselyov <jeka911@gmail.com> (https://github.com/jeka-kiselyov)",
11
22
  "license": "Apache-2.0",
12
23
  "dependencies": {
13
24
  "@mysten/sui.js": "^0.34.0",
14
25
  "@wallet-standard/core": "^1.0.3"
26
+ },
27
+ "devDependencies": {
28
+ "tap": "^16.3.4"
29
+ },
30
+ "tap": {
31
+ "branches": 90,
32
+ "lines": 90,
33
+ "functions": 90,
34
+ "statements": 90
15
35
  }
16
- }
36
+ }
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap');
4
+ const { test } = t;
5
+
6
+ const { SuiMaster } = require('..');
7
+
8
+ test('initialization', async t => {
9
+ t.plan(2);
10
+
11
+ t.ok(true);
12
+ t.equal(1, 1, 'Ready state is (==1)');
13
+ });
14
+
15
+ test('pseudo-random keypairs generation works ok', async t => {
16
+ const suiMaster = new SuiMaster({provider: 'test', as: 'somebody'});
17
+ await suiMaster.initialize();
18
+
19
+ // pseudo-random generation of 'somebody' is a keypair for a wallet '0x15b9493fb639a3118fed766ca80c1da62fa20493c293f319cc7d136506d2db69'
20
+ // not sure if we need to assert it, as we may change pseudo-random generation algo, still keeping it function,
21
+ // so lets just check we make different keypairs depending on 'as' input parameter
22
+ // console.log(suiMaster.address);
23
+
24
+ t.ok(suiMaster.address); // there should be some address
25
+ t.ok(`${suiMaster.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
26
+
27
+ const suiMasterAsAdmin = new SuiMaster({provider: 'test', as: 'admin'});
28
+ await suiMasterAsAdmin.initialize();
29
+
30
+ t.ok(suiMasterAsAdmin.address); // there should be some address
31
+ t.ok(`${suiMasterAsAdmin.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
32
+
33
+ t.not(`${suiMaster.address}`, `${suiMasterAsAdmin.address}`, 'different pseudo randoms should be different');
34
+
35
+ /// but if you pass the same string as 'as' - it will generate the same keypair:
36
+ const suiMasterAsAdminAnother = new SuiMaster({provider: 'test', as: 'admin'});
37
+ await suiMasterAsAdminAnother.initialize();
38
+
39
+ t.equal(`${suiMasterAsAdminAnother.address}`, `${suiMasterAsAdmin.address}`, 'same string should generate same pseudo-random');
40
+ });
41
+
42
+ test('connecting to different chains', async t => {
43
+ const suiMaster = new SuiMaster({provider: 'test', as: 'somebody'});
44
+ await suiMaster.initialize();
45
+
46
+ t.equal(suiMaster.connectedChain, 'sui:testnet');
47
+
48
+ const suiMaster2 = new SuiMaster({provider: 'dev', as: 'somebody'});
49
+ await suiMaster2.initialize();
50
+
51
+ t.equal(suiMaster2.connectedChain, 'sui:devnet');
52
+
53
+ const suiMaster3 = new SuiMaster({provider: 'main', as: 'somebody'});
54
+ await suiMaster3.initialize();
55
+
56
+ t.equal(suiMaster3.connectedChain, 'sui:mainnet');
57
+
58
+ const suiMaster4 = new SuiMaster({provider: 'local', as: 'somebody'});
59
+ await suiMaster4.initialize();
60
+
61
+ t.equal(suiMaster4.connectedChain, 'sui:localnet');
62
+ });
@@ -0,0 +1,211 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap');
4
+ const { test } = t;
5
+ const path = require('path');
6
+
7
+ const { SuiMaster, SuiLocalTestValidator } = require('..');
8
+
9
+ let suiLocalTestValidator = null;
10
+ let suiMaster = null;
11
+ let contract = null;
12
+
13
+ let contractAddressV1 = null;
14
+ let contractAddressV2 = null;
15
+
16
+ let chatShopObjectId = null;
17
+
18
+ test('spawn local test node', async t => {
19
+ suiLocalTestValidator = await SuiLocalTestValidator.launch();
20
+ t.ok(suiLocalTestValidator.active);
21
+
22
+ // SuiLocalTestValidator runs as signle instance. So you can't start it twice with static method
23
+ const suiLocalTestValidatorCopy = await SuiLocalTestValidator.launch();
24
+ t.equal(suiLocalTestValidator, suiLocalTestValidatorCopy);
25
+ });
26
+
27
+ test('init suiMaster and connect it to local test validator', async t => {
28
+ suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody'});
29
+ await suiMaster.initialize();
30
+
31
+ t.ok(suiMaster.address); // there should be some address
32
+ t.ok(`${suiMaster.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
33
+ });
34
+
35
+ test('request sui from faucet', async t => {
36
+ const balanceBefore = await suiMaster.getBalance();
37
+ await suiMaster.requestSuiFromFaucet();
38
+
39
+ const balanceAfter = await suiMaster.getBalance();
40
+
41
+ t.ok(balanceAfter > balanceBefore);
42
+ });
43
+
44
+ test('attach a local package', async t => {
45
+ contract = suiMaster.addPackage({
46
+ path: path.join(__dirname, './test_move_contracts/suidouble_chat/'),
47
+ });
48
+ // there's nothing in contract yet, it's not built, not published (don't know it's id on chain)
49
+ //
50
+ // we can check that contract's objectStorage is the same instance as on master. Reminder, it's shared between all connections to same chain
51
+ t.equal(contract.objectStorage, suiMaster.objectStorage);
52
+
53
+ // lets try to build it
54
+ await contract.build();
55
+
56
+ t.ok(contract.isBuilt);
57
+
58
+ // and publish
59
+
60
+ await contract.publish();
61
+
62
+ t.ok(contract.address); // there should be some address
63
+ t.ok(contract.id); // same as id
64
+ t.ok(`${contract.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
65
+
66
+ t.equal(contract.version, 1);
67
+
68
+ // there should be module 'suidouble_chat' on the contract we published
69
+ t.ok(contract.modules.suidouble_chat);
70
+
71
+ // we can check that contract's objectStorage is the same instance as on master. Reminder, it's shared between all connections to same chain
72
+ t.equal(contract.modules.suidouble_chat.objectStorage, suiMaster.objectStorage);
73
+
74
+ contractAddressV1 = contract.address;
75
+
76
+ // we'd need to .build() again after changes here. But it lets you upgrade package with very same code
77
+ await contract.upgrade();
78
+
79
+ t.not(contract.address, contractAddressV1);
80
+ t.equal(contract.version, 2);
81
+
82
+ contractAddressV2 = contract.address;
83
+
84
+ // let's quickly check it worked, there should be event ChatShopCreated created and we can fetch it from contract's module
85
+ const eventsResponse = await contract.modules.suidouble_chat.fetchEvents();
86
+ // response is an instance of SuiPaginatedResponse
87
+ let foundChatShopCreatedEvent = false;
88
+ for (const event of eventsResponse.data) {
89
+ if (event.typeName === 'ChatShopCreated') {
90
+ foundChatShopCreatedEvent = true;
91
+ }
92
+ }
93
+
94
+ t.ok(foundChatShopCreatedEvent);
95
+ });
96
+
97
+ test('attach a package by address on the blockchain', async t => {
98
+ suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody'});
99
+ await suiMaster.initialize();
100
+
101
+ contract = await suiMaster.addPackage({
102
+ id: contractAddressV2,
103
+ });
104
+ const eventsResponse = await contract.fetchEvents('suidouble_chat');
105
+
106
+ let foundChatShopCreatedEvent = false;
107
+ for (const event of eventsResponse.data) {
108
+ if (event.typeName === 'ChatShopCreated') {
109
+ foundChatShopCreatedEvent = true;
110
+ chatShopObjectId = event.parsedJson.id;
111
+ }
112
+ }
113
+
114
+ // there should be ChatShopCreated event
115
+ t.ok(foundChatShopCreatedEvent);
116
+
117
+ // it should have id of ChatShop object
118
+ t.ok(chatShopObjectId);
119
+
120
+ // there should be module 'suidouble_chat' on the contract
121
+ t.ok(contract.modules.suidouble_chat);
122
+ });
123
+
124
+ test('can find a package on the blockchain by expected module name (in owned)', async t => {
125
+ suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody'});
126
+ await suiMaster.initialize();
127
+
128
+ contract = await suiMaster.addPackage({
129
+ modules: ['suidouble_chat'],
130
+ });
131
+ const eventsResponse = await contract.fetchEvents('suidouble_chat');
132
+
133
+ let foundChatShopCreatedEvent = false;
134
+ for (const event of eventsResponse.data) {
135
+ if (event.typeName === 'ChatShopCreated') {
136
+ foundChatShopCreatedEvent = true;
137
+ chatShopObjectId = event.parsedJson.id;
138
+ }
139
+ }
140
+
141
+ // there should be ChatShopCreated event
142
+ t.ok(foundChatShopCreatedEvent);
143
+ // it should have id of ChatShop object
144
+ t.ok(chatShopObjectId);
145
+
146
+ // there should be module 'suidouble_chat' on the contract
147
+ t.ok(contract.modules.suidouble_chat);
148
+
149
+ // it should find most recent version of the package
150
+ t.equal(contract.version, 2);
151
+ });
152
+
153
+ test('execute contract methods', async t => {
154
+ const moveCallResult = await contract.moveCall('suidouble_chat', 'post', [chatShopObjectId, 'the message', 'metadata']);
155
+
156
+ // there're at least some object created
157
+ t.ok(moveCallResult.created.length > 0);
158
+
159
+ // by suidouble_chat contract design, ChatTopMessage is an object representing a thread,
160
+ // it always has at least one ChatResponse (with text of the very first message in thread)
161
+ let foundChatTopMessage = null;
162
+ let foundChatResponse = null;
163
+ let foundText = null;
164
+ moveCallResult.created.forEach((obj)=>{
165
+ if (obj.typeName == 'ChatTopMessage') {
166
+ foundChatTopMessage = true;
167
+ }
168
+ if (obj.typeName == 'ChatResponse') {
169
+ foundChatResponse = true;
170
+ foundText = obj.fields.text;
171
+ }
172
+ });
173
+
174
+ t.ok(foundChatTopMessage);
175
+ t.ok(foundChatResponse);
176
+
177
+ // messageTextAsBytes = [].slice.call(new TextEncoder().encode(messageText)); // regular array with utf data
178
+ // suidouble_chat contract store text a bytes (easier to work with unicode things), let's convert it back to js string
179
+ foundText = new TextDecoder().decode(new Uint8Array(foundText));
180
+
181
+ t.equal(foundText, 'the message');
182
+
183
+ // now lets post a reply to the thread
184
+ // we need a ChatTopMessage to pass to move's 'reply' function
185
+ // we can find it via ChatTopMessageCreated events or from previous method execution results
186
+ // find from local objectStorage (where previous results are stored)
187
+ const chatTopMessage = contract.objectStorage.findMostRecentByTypeName('ChatTopMessage');
188
+ t.ok(chatTopMessage);
189
+
190
+ const responseTextAsBytes = [].slice.call(new TextEncoder().encode('ขอบคุณครับ, 🇺🇦')); // regular array with utf data
191
+ const moveCallResult2 = await contract.moveCall('suidouble_chat', 'reply', [chatTopMessage.id, responseTextAsBytes, 'metadata']);
192
+
193
+ // there're at least some object created
194
+ t.ok(moveCallResult2.created.length > 0);
195
+
196
+ let responseText = null;
197
+ moveCallResult2.created.forEach((obj)=>{
198
+ if (obj.typeName == 'ChatResponse') {
199
+ responseText = obj.fields.text;
200
+ }
201
+ });
202
+ // messageTextAsBytes = [].slice.call(new TextEncoder().encode(messageText)); // regular array with utf data
203
+ // suidouble_chat contract store text a bytes (easier to work with unicode things), let's convert it back to js string
204
+ responseText = new TextDecoder().decode(new Uint8Array(responseText));
205
+
206
+ t.equal(responseText, 'ขอบคุณครับ, 🇺🇦');
207
+ });
208
+
209
+ test('stops local test node', async t => {
210
+ SuiLocalTestValidator.stop();
211
+ });
@@ -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_chat"
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_chat = "0x0"
10
+ sui = "0000000000000000000000000000000000000000000000000000000000000002"
@@ -0,0 +1 @@
1
+ {"modules":["oRzrCwYAAAALAQAQAhAyA0JQBJIBGgWsAYcBB7MCtQMI6AVABqgGKArQBlEMoQfQAg3xCQYAJAEOARIBFAEaASMBKAEpAAUDAAAHAwAAAwMAAAEIAAAECAAABgwAAAIMAAEABAEAAQQIBwAECwQABQkCAAcKAgAAFwABAAAbAgEAAB4DAQABKwEOAQACDBcBAgcMAhUZGgEHAh0bHAIHDAMTDAEBAwQWFAoBCAQZAAQABCoJCgAGIgwBAQgGKAgBAQgHIAUGAAwHBwsDDQsPBxIHEwgPCBUEFgsVBRgGFgwdAQcICwAEBggECgIKAgcICwQHCAUKAgoCBwgLAQgJAQYICwEFAQgDAgkABQEGCAkBCAgBCAABCQABCAoBCwcBCQABCAQICAkICAUICAgGCAkIBQgJAQIBCAEBCAIBBgkAAQgFAgoCCAYDBwgJCQAJAQEKAgIGCAkJAAEBAgcICQkAAQkBAQgGB0JhbGFuY2UMQ2hhdE93bmVyQ2FwDENoYXRSZXNwb25zZRNDaGF0UmVzcG9uc2VDcmVhdGVkCENoYXRTaG9wD0NoYXRTaG9wQ3JlYXRlZA5DaGF0VG9wTWVzc2FnZRVDaGF0VG9wTWVzc2FnZUNyZWF0ZWQCSUQDU1VJCVR4Q29udGV4dANVSUQDYWRkBmF1dGhvcgdiYWxhbmNlDGNoYXRfc2hvcF9pZBNjaGF0X3RvcF9tZXNzYWdlX2lkFGNoYXRfdG9wX3Jlc3BvbnNlX2lkFGR5bmFtaWNfb2JqZWN0X2ZpZWxkBGVtaXQFZXZlbnQHZXhpc3RzXwJpZARpbml0CG1ldGFkYXRhA25ldwZvYmplY3QEcG9zdAVwcmljZQZyZW1vdmUFcmVwbHkPcmVzcG9uc2VzX2NvdW50BnNlbmRlcgVzZXFfbgxzaGFyZV9vYmplY3QDc3VpDnN1aWRvdWJsZV9jaGF0BHRleHQOdG9wX21lc3NhZ2VfaWQPdG9wX3Jlc3BvbnNlX2lkCHRyYW5zZmVyCnR4X2NvbnRleHQMdWlkX3RvX2lubmVyBHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgAAgAAAAAAAAMIAAAAAAAAAAAKAhEQYXNfY2hhdF9yZXNwb25zZQACARYICAECAhYICCcICAICAxYICCYICCEDAwIBFggJBAIDFggJHAMOCwcBCAoFAgUWCAkPCAgRCAgNBR8DBgIGFggJEAgIDQUlCgIYCgIhAwAAAAAEFAoAEQkSAwoALhENOAALABEJDAEOAREKEgA4AQsBBugDAAAAAAAAOAISBDgDAgEBBAAQRQ4BQREHACUEBgUMCwMBCwABBwEnCgMRCQwLCgMRCQwJDgsRCg4JEQoSATgEDgkRCg4LEQoGAAAAAAAAAAASAjgFCwsMBAsAOAYMBQoDLhENDAYOCREKDAcLBAsFCwcLBgYAAAAAAAAAABIFDAoLCQ4KOAcLAy4RDQsBCwIGAAAAAAAAAAASBgwIDQoPAAcCCwg4CAsKOAkCAgEEAARADgFBEQcAJQQGBQwLAwELAAEHAScKABAABwI4CgQZCgAPAAcCOAsKABABFDgMCgAQAhQGAQAAAAAAAAAWCgAPAhUKAxEJDAQOBBEKCgAQABEKCgAQAhQSAjgFCwQKAC44BwoDLhENCwELAgsAEAIUEgYLAy4RDTgMAgUABQMFBAA="],"dependencies":["0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002"],"digest":[160,183,189,147,87,240,81,132,236,190,75,225,74,68,254,14,106,61,173,195,201,119,140,108,230,103,29,238,48,162,45,170]}
@@ -0,0 +1,221 @@
1
+
2
+ module suidouble_chat::suidouble_chat {
3
+ use sui::object::{Self, UID, ID};
4
+ use sui::transfer;
5
+ use sui::tx_context::{Self, TxContext};
6
+ use std::vector::length;
7
+
8
+ use sui::dynamic_object_field::{Self};
9
+
10
+ use sui::sui::SUI;
11
+ use sui::balance::{Self, Balance};
12
+
13
+ use std::debug;
14
+
15
+ use sui::event::emit;
16
+
17
+ /// Max text length.
18
+ const MAX_TEXT_LENGTH: u64 = 512;
19
+
20
+ /// Text size overflow.
21
+ const ETextOverflow: u64 = 0;
22
+
23
+ // ======== Events =========
24
+
25
+ /// Event. When a new chat has been created.
26
+ struct ChatShopCreated has copy, drop { id: ID }
27
+ struct ChatTopMessageCreated has copy, drop { id: ID, top_response_id: ID }
28
+ struct ChatResponseCreated has copy, drop { id: ID, top_message_id: ID, seq_n: u64 }
29
+
30
+ /// Capability that grants an owner the right to collect profits.
31
+ struct ChatOwnerCap has key { id: UID }
32
+
33
+ /// A shared object. `key` ability is required.
34
+ struct ChatShop has key {
35
+ id: UID,
36
+ price: u64,
37
+ balance: Balance<SUI>
38
+ }
39
+
40
+ struct ChatTopMessage has key, store {
41
+ id: UID,
42
+ chat_shop_id: ID,
43
+ chat_top_response_id: ID,
44
+ author: address,
45
+ responses_count: u64,
46
+ }
47
+
48
+ struct ChatResponse has key, store {
49
+ id: UID,
50
+ chat_top_message_id: ID,
51
+ author: address,
52
+ text: vector<u8>,
53
+ // app-specific metadata. We do not enforce a metadata format and delegate this to app layer.
54
+ metadata: vector<u8>,
55
+ seq_n: u64, // n of message in thread
56
+ }
57
+
58
+ /// Init function is often ideal place for initializing
59
+ /// a shared object as it is called only once.
60
+ ///
61
+ /// To share an object `transfer::share_object` is used.
62
+ fun init(ctx: &mut TxContext) {
63
+ transfer::transfer(ChatOwnerCap {
64
+ id: object::new(ctx)
65
+ }, tx_context::sender(ctx));
66
+
67
+ let id = object::new(ctx);
68
+ emit(ChatShopCreated { id: object::uid_to_inner(&id) });
69
+
70
+ // Share the object to make it accessible to everyone!
71
+ transfer::share_object(ChatShop {
72
+ id: id,
73
+ price: 1000,
74
+ balance: balance::zero()
75
+ })
76
+ }
77
+
78
+ // /// Simple ChatResponse.text getter.
79
+ // public fun text(chat_response: &ChatResponse): String {
80
+ // chat_response.text
81
+ // }
82
+
83
+ /// Mint (post) a chatMessage object without referencing another object.
84
+ public entry fun post(
85
+ chat_shop: &ChatShop,
86
+ text: vector<u8>,
87
+ metadata: vector<u8>,
88
+ ctx: &mut TxContext,
89
+ ) {
90
+ assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow);
91
+ let id = object::new(ctx);
92
+ let chat_response_id = object::new(ctx);
93
+
94
+ emit(ChatTopMessageCreated { id: object::uid_to_inner(&id), top_response_id: object::uid_to_inner(&chat_response_id), });
95
+ emit(ChatResponseCreated { id: object::uid_to_inner(&chat_response_id), top_message_id: object::uid_to_inner(&id), seq_n: 0 });
96
+
97
+ let chat_top_message = ChatTopMessage {
98
+ id: id,
99
+ chat_shop_id: object::id(chat_shop),
100
+ author: tx_context::sender(ctx),
101
+ chat_top_response_id: object::uid_to_inner(&chat_response_id),
102
+ responses_count: 0,
103
+ };
104
+
105
+ let chat_response = ChatResponse {
106
+ id: chat_response_id,
107
+ chat_top_message_id: object::id(&chat_top_message),
108
+ author: tx_context::sender(ctx),
109
+ text: text,
110
+ metadata,
111
+ seq_n: 0,
112
+ };
113
+ dynamic_object_field::add(&mut chat_top_message.id, b"as_chat_response", chat_response);
114
+
115
+ transfer::share_object(chat_top_message);
116
+ }
117
+
118
+ public entry fun reply(
119
+ chat_top_message: &mut ChatTopMessage,
120
+ text: vector<u8>,
121
+ metadata: vector<u8>,
122
+ ctx: &mut TxContext,
123
+ ) {
124
+ assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow);
125
+
126
+ let dynamic_field_exists = dynamic_object_field::exists_(&chat_top_message.id, b"as_chat_response");
127
+ if (dynamic_field_exists) {
128
+ let top_level_chat_response = dynamic_object_field::remove<vector<u8>, ChatResponse>(&mut chat_top_message.id, b"as_chat_response");
129
+ transfer::transfer(top_level_chat_response, chat_top_message.author);
130
+ };
131
+
132
+ chat_top_message.responses_count = chat_top_message.responses_count + 1;
133
+
134
+ let id = object::new(ctx);
135
+
136
+ emit(ChatResponseCreated { id: object::uid_to_inner(&id), top_message_id: object::uid_to_inner(&chat_top_message.id), seq_n: chat_top_message.responses_count });
137
+
138
+ let chat_response = ChatResponse {
139
+ id: id,
140
+ chat_top_message_id: object::id(chat_top_message),
141
+ author: tx_context::sender(ctx),
142
+ text: text,
143
+ metadata,
144
+ seq_n: chat_top_message.responses_count,
145
+ };
146
+
147
+ transfer::transfer(chat_response, tx_context::sender(ctx));
148
+ }
149
+
150
+
151
+
152
+ #[test]
153
+ public fun test_module_init() {
154
+ use sui::test_scenario;
155
+
156
+ // Create test address representing game admin
157
+ let admin = @0xBABE;
158
+ let somebody = @0xFAFE;
159
+ let anybody = @0xFAAE;
160
+ // let player = @0x0;
161
+
162
+ // First transaction to emulate module initialization
163
+ let scenario_val = test_scenario::begin(admin);
164
+ let scenario = &mut scenario_val;
165
+
166
+ // Run the module initializers
167
+ test_scenario::next_tx(scenario, admin);
168
+ {
169
+ init(test_scenario::ctx(scenario));
170
+
171
+ };
172
+ // Run the module initializers
173
+ test_scenario::next_tx(scenario, somebody);
174
+ {
175
+ let chat_shop = test_scenario::take_shared<ChatShop>(scenario);
176
+ // let chat_shop_ref = &chat_shop;
177
+ debug::print(&chat_shop);
178
+
179
+ let chat_shop_ref = &chat_shop;
180
+ post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
181
+
182
+ // post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
183
+
184
+ test_scenario::return_shared(chat_shop);
185
+ };
186
+
187
+ test_scenario::next_tx(scenario, anybody);
188
+ {
189
+ // let chat_top_message = test_scenario::take_from_sender<ChatTopMessage>(scenario);
190
+ let chat_top_message = test_scenario::take_shared<ChatTopMessage>(scenario);
191
+ // let chat_shop_ref = &chat_shop;
192
+ debug::print(&chat_top_message);
193
+ debug::print(&mut chat_top_message);
194
+
195
+ // let chat_top_message_ref = &chat_top_message;
196
+ reply(&mut chat_top_message, b"response", b"metadata", test_scenario::ctx(scenario));
197
+
198
+ // post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
199
+
200
+ test_scenario::return_shared(chat_top_message);
201
+ // test_scenario::return_to_sender(scenario, chat_top_message);
202
+ };
203
+
204
+ test_scenario::next_tx(scenario, anybody);
205
+ {
206
+ // let chat_top_message = test_scenario::take_from_sender<ChatTopMessage>(scenario);
207
+ let chat_top_message = test_scenario::take_shared<ChatTopMessage>(scenario);
208
+ // let chat_shop_ref = &chat_shop;
209
+ debug::print(&mut chat_top_message);
210
+
211
+ // let chat_top_message_ref = &chat_top_message;
212
+ reply(&mut chat_top_message, b"response", b"metadata", test_scenario::ctx(scenario));
213
+
214
+ // post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
215
+
216
+ test_scenario::return_shared(chat_top_message);
217
+ // test_scenario::return_to_sender(scenario, chat_top_message);
218
+ };
219
+ test_scenario::end(scenario_val);
220
+ }
221
+ }