+7
| const AA = require('./libs/aa'); | ||
| const AAs = require('./libs/aas'); | ||
| module.exports = { | ||
| AA, | ||
| AAs | ||
| } |
+94
| const EventEmitter = require('events'); | ||
| const network = require('ocore/network'); | ||
| const eventBus = require('ocore/event_bus'); | ||
| class AA { | ||
| constructor(address) { | ||
| this.address = address; | ||
| this.__listen(); | ||
| this.arrRequestCBs = []; | ||
| this.arrResponseCBs = []; | ||
| this.arrDefinitionCBs = []; | ||
| this.arrDefinitionSavedCBs = []; | ||
| this.events = new EventEmitter(); | ||
| eventBus.on('message_for_light', async (ws, subject, body) => { | ||
| if (subject === 'light/aa_request' && body.aa_address === this.address) { | ||
| const params = {address: body.aa_address, messages: body.unit.messages}; | ||
| this.events.emit('new_request', params, body); | ||
| this.arrRequestCBs.forEach(obj => { | ||
| if (obj.func(params, body)) { | ||
| obj.cb(params, body); | ||
| } | ||
| }); | ||
| } else if (subject === 'light/aa_response' && body.aa_address === this.address) { | ||
| const err = body.bounced ? body.response.error : false; | ||
| let vars = !err ? await AA.getAAVars(this.address) : undefined; | ||
| const params = { address: body.aa_address, response: body.response }; | ||
| this.events.emit('new_response', err, params, vars, body); | ||
| this.arrResponseCBs.forEach(obj => { | ||
| if (obj.func(err, params, vars, body)) { | ||
| obj.cb(err, params, vars, body); | ||
| } | ||
| }); | ||
| } else if (subject === 'light/aa_definition') { | ||
| let def = body.messages.find(v => v.app === 'definition'); | ||
| if(def) def = def.payload; | ||
| this.events.emit('new_aa_definition', def, body); | ||
| this.arrDefinitionCBs.forEach(obj => { | ||
| if (obj.func(def, body)) { | ||
| obj.cb(def, body); | ||
| } | ||
| }); | ||
| } else if (subject === 'light/aa_definition_saved') { | ||
| let def = body.messages.find(v => v.app === 'definition'); | ||
| if (def) def = def.payload; | ||
| this.events.emit('new_aa_definition_saved', def, body); | ||
| this.arrDefinitionSavedCBs.forEach(obj => { | ||
| if (obj.func(def, body)) { | ||
| obj.cb(def, body); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| addRequestEventHandler(func, cb) { | ||
| this.arrRequestCBs.push({ func, cb }); | ||
| } | ||
| addResponseEventHandler(func, cb) { | ||
| this.arrResponseCBs.push({ func, cb }); | ||
| } | ||
| addDefinitionEventHandler(func, cb) { | ||
| this.arrDefinitionCBs.push({ func, cb }); | ||
| } | ||
| addDefinitionSavedEventHandler(func, cb) { | ||
| this.arrDefinitionSavedCBs.push({ func, cb }); | ||
| } | ||
| __listen() { | ||
| network.addLightWatchedAa(this.address, undefined, err => { | ||
| if (err) | ||
| throw new Error(err); | ||
| }); | ||
| } | ||
| static getAAVars(address) { | ||
| return new Promise(resolve => { | ||
| network.requestFromLightVendor('light/get_aa_state_vars', | ||
| { address }, | ||
| (ws, request, response) => { | ||
| return resolve(response); | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| module.exports = AA; |
+81
| const EventEmitter = require('events'); | ||
| const network = require('ocore/network'); | ||
| const eventBus = require('ocore/event_bus'); | ||
| class AAs { | ||
| constructor(startAddresses) { | ||
| if (typeof startAddresses !== 'object') throw Error('parameter must be an array'); | ||
| this.addresses = startAddresses; | ||
| this.__listenAllAddresses(); | ||
| this.arrRequestCBs = []; | ||
| this.arrResponseCBs = []; | ||
| this.events = new EventEmitter(); | ||
| eventBus.on('message_for_light', async (ws, subject, body) => { | ||
| if (subject === 'light/aa_request' && this.addresses.includes(body.aa_address)) { | ||
| const params = { address: body.aa_address, messages: body.unit.messages }; | ||
| this.events.emit('new_request', params, body); | ||
| this.arrRequestCBs.forEach(obj => { | ||
| if (obj.func(params, body)) { | ||
| obj.cb(params, body); | ||
| } | ||
| }); | ||
| } else if (subject === 'light/aa_response' && this.addresses.includes(body.aa_address)) { | ||
| const err = body.bounced ? body.response.error : false; | ||
| let vars = !err ? await AAs.getAAVars(body.aa_address) : undefined; | ||
| const params = { address: body.aa_address, response: body.response }; | ||
| this.events.emit('new_response', err, params, vars, body); | ||
| this.arrResponseCBs.forEach(obj => { | ||
| if (obj.func(err, params, vars, body)) { | ||
| obj.cb(err, params, vars, body); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| addRequestEventHandler(func, cb) { | ||
| this.arrRequestCBs.push({ func, cb }); | ||
| } | ||
| addResponseEventHandler(func, cb) { | ||
| this.arrResponseCBs.push({ func, cb }); | ||
| } | ||
| addAddress(address) { | ||
| this.addresses.push(address); | ||
| this.__listen(address); | ||
| } | ||
| __listen(address) { | ||
| network.addLightWatchedAa(address, undefined, err => { | ||
| if (err) | ||
| throw new Error(err); | ||
| }); | ||
| } | ||
| __listenAllAddresses() { | ||
| this.addresses.forEach(address => { | ||
| network.addLightWatchedAa(address, undefined, err => { | ||
| if (err) | ||
| throw new Error(err); | ||
| }); | ||
| }) | ||
| } | ||
| static getAAVars(address) { | ||
| return new Promise(resolve => { | ||
| network.requestFromLightVendor('light/get_aa_state_vars', | ||
| { address }, | ||
| (ws, request, response) => { | ||
| return resolve(response); | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| module.exports = AAs; |
+6
-14
| { | ||
| "name": "aagent.js", | ||
| "version": "0.0.6", | ||
| "description": "It is CLI to work with Autonomous Agents on Obyte", | ||
| "version": "0.0.7", | ||
| "description": "It is library to work with Autonomous Agents on Obyte", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| }, | ||
| "bin": { | ||
| "aagent": "./cli.js" | ||
| "dependencies": { | ||
| "headless-obyte": "https://github.com/byteball/headless-obyte" | ||
| }, | ||
| "keywords": [ | ||
| "obyte", | ||
| "framework", | ||
| "library", | ||
| "aa", | ||
@@ -27,12 +28,3 @@ "autonomous", | ||
| "url": "https://github.com/olabs-org/aagent.js/issues" | ||
| }, | ||
| "dependencies": { | ||
| "fs-extra": "^9.0.0", | ||
| "global": "^4.4.0", | ||
| "ocore": "git+https://github.com/byteball/ocore.git", | ||
| "open": "^7.0.4", | ||
| "request": "^2.88.2", | ||
| "request-promise": "^4.2.5", | ||
| "yargs": "^15.3.1" | ||
| } | ||
| } |
+172
-21
@@ -1,40 +0,191 @@ | ||
| # aagent.js | ||
| ### What's it? | ||
| It is CLI and [library](https://github.com/olabs-org/aagent.js-lib) to work with Autonomous Agents on Obyte | ||
| It is [CLI](https://github.com/olabs-org/aagent-cli) and library to work with Autonomous Agents on Obyte | ||
| ## CLI | ||
| Init | ||
| ### Quick start | ||
| You can create a new project using [CLI](https://github.com/olabs-org/aagent-cli) | ||
| ```bash | ||
| npm i -g aagent.js | ||
| aagent --help | ||
| npm i -g aagent-cli | ||
| aagent init folder | ||
| ``` | ||
| Create project from [template](template) (with AA example, tests and library) | ||
| Or add to an existing project | ||
| ```bash | ||
| aagent init folder | ||
| yarn add aagent.js | ||
| ``` | ||
| ```javascript | ||
| require('headless-obyte'); | ||
| const eventBus = require('ocore/event_bus'); | ||
| const { AA, AAs } = require('aagent.js'); | ||
| ``` | ||
| ## Library | ||
| ### AA | ||
| Example: | ||
| ```javascript | ||
| require('headless-obyte'); | ||
| const eventBus = require('ocore/event_bus'); | ||
| const { AA } = require('aagent.js'); | ||
| eventBus.on('headless_wallet_ready', () => { | ||
| const aa = new AA('address'); | ||
| aa.events.on('new_request', (request) => { | ||
| console.error('new request', request); | ||
| }); | ||
| aa.events.on('new_response', (err, response, vars) => { | ||
| console.error('new response', response, vars); | ||
| }); | ||
| aa.events.on('new_aa_definition', (definition) => { | ||
| console.error('new aa definition', definition); | ||
| }); | ||
| aa.events.on('new_aa_definition_saved', (definition) => { | ||
| console.error('new aa definition saved', definition); | ||
| }); | ||
| aa.newResponseFilter((err, params, vars) => { | ||
| return true; | ||
| }, (err, params, vars) => { | ||
| console.error(err, params, vars); | ||
| }); | ||
| }); | ||
| ``` | ||
| <br> | ||
| Validating AA | ||
| ```bash | ||
| aagent validate aa.oscript | ||
| #### AA class contains the following methods: | ||
| All events are applied to the specified address in the constructor. | ||
| ##### constructor | ||
| ```javascript | ||
| const aa = new AA('address') | ||
| ``` | ||
| one argument - aa_address(string) | ||
| static method **getAAVars**: | ||
| ```javascript | ||
| AA.getAAVars('address'); | ||
| ``` | ||
| will return variables from AA. | ||
| <br> | ||
| Open GUI Wallet to deploy | ||
| ```bash | ||
| aagent deploy aa.oscript | ||
| #### Event handlers: | ||
| The handler triggers an event, passes it to the first function, and if it returns true, calls the second function. You can use them to process specific data(like a router). | ||
| <br>**More about arguments in events.** | ||
| <br> | ||
| ```javascript | ||
| aa.addRequestEventHandler((request, body) => { | ||
| return true; | ||
| }, (request, body) => { | ||
| console.error(request, body); | ||
| }); | ||
| ``` | ||
| *If the file is too large it will be uploaded to the server and transferred to the client in the link.* <br> | ||
| This command supports argument **--testnet** to deploy script through the testnet or mainnet wallet. | ||
| ```javascript | ||
| aa.addResponseEventHandler((err, params, vars, body) => { | ||
| return true; | ||
| }, (err, params, vars, body) => { | ||
| console.error(err, params, vars, body); | ||
| }); | ||
| ``` | ||
| ```javascript | ||
| aa.addDefinitionEventHandler((definition, body) => { | ||
| return true; | ||
| }, (definition, body) => { | ||
| console.error(definition, body); | ||
| }); | ||
| ``` | ||
| ```javascript | ||
| aa.addDefinitionSavedEventHandler((definition, body) => { | ||
| return true; | ||
| }, (definition, body) => { | ||
| console.error(definition, body); | ||
| }); | ||
| ``` | ||
| <br> | ||
| #### And events: | ||
| ##### new_request | ||
| ```javascript | ||
| aa.events.on('new_request', (request, body) => { | ||
| console.error('new request', request, body); | ||
| }); | ||
| ``` | ||
| Arguments: | ||
| - request - {address: aa_address, messages: unit.messages} | ||
| - body - raw body from event | ||
| <br> | ||
| Start tests | ||
| ```bash | ||
| yarn test | ||
| ##### new_response | ||
| ```javascript | ||
| aa.events.on('new_response', (err, params, vars, body) => { | ||
| console.error('new response', err, params, vars, body); | ||
| }); | ||
| ``` | ||
| Arguments: | ||
| - err - if *bounced* return error message | ||
| - params - { address: aa_address, response: body.response } | ||
| - vars - new vars from AA | ||
| - body - raw body from event | ||
| <br> | ||
| #### If you want to start developing on Obyte and you need help, write to us on [discord](https://obyte.org/discord) or on telegram: @xJeneK | ||
| ##### new_aa_definition | ||
| ```javascript | ||
| aa.events.on('new_aa_definition', (definition, body) => { | ||
| console.error('new aa definition', definition, body); | ||
| }); | ||
| ``` | ||
| Arguments: | ||
| - definition - definition from messages | ||
| - body - raw body from event | ||
| <br> | ||
| ##### new_aa_definition_saved | ||
| ```javascript | ||
| aa.events.on('new_aa_definition_saved', (definition, body) => { | ||
| console.error('new aa definition saved', definition, body); | ||
| }); | ||
| ``` | ||
| *This event can be triggered twice with the same data (architectural features), please consider this.*<br> | ||
| Arguments: | ||
| - definition - definition from messages | ||
| - body - raw body from event | ||
| --- | ||
| ### AAs | ||
| All events applies to the specified addresses in the constructor. | ||
| ##### constructor | ||
| ```javascript | ||
| const aas = new AAs(['address', 'address2']) | ||
| ``` | ||
| one argument - aa_addresses - array[string] | ||
| <br> | ||
| ```javascript | ||
| aas.addAddress('address'); | ||
| ``` | ||
| Adds a new address and subscribes to it | ||
| <br> | ||
| static method **getAAVars**: | ||
| ```javascript | ||
| AAs.getAAVars('address'); | ||
| ``` | ||
| will return variables from AA. | ||
| This class supports only: <br> | ||
| **Methods**: addRequestEventHandler, addResponseEventHandler <br> | ||
| **Events**: new_request, new_response<br><br> | ||
| **Arguments are identical to AA class** | ||
| <br> | ||
| #### If you want to start developing on Obyte and you need help, write to us on [discord](https://obyte.org/discord) or on telegram: @xJeneK |
-41
| #!/usr/bin/env node | ||
| const yargs = require('yargs'); | ||
| const commands = require('./commands'); | ||
| const argv = | ||
| yargs | ||
| .scriptName('aagent') | ||
| .options({ | ||
| 'testnet': { | ||
| alias: 't', | ||
| default: false, | ||
| describe: 'testnet', | ||
| type: 'boolean' | ||
| } | ||
| }) | ||
| .command('init [folder]', 'init project', yargs => { | ||
| yargs.positional('folder', { | ||
| description: 'folder for project' | ||
| }); | ||
| }, async argv => { | ||
| await commands.init(argv.folder); | ||
| }) | ||
| .command('validate [file]', 'aa file validation', yargs => { | ||
| yargs.positional('file', { | ||
| description: 'path to oscript file' | ||
| }) | ||
| }, async argv => { | ||
| await commands.validate(argv.file); | ||
| }) | ||
| .command('deploy [file]', 'Open wallet for deploy', yargs => { | ||
| yargs.positional('file', { | ||
| description: 'path to oscript file' | ||
| }) | ||
| }, async argv => { | ||
| await commands.deploy(argv.file, argv.testnet); | ||
| }) | ||
| .showHelpOnFail(true) | ||
| .demandCommand(1, '') | ||
| .recommendCommands() | ||
| .strict() | ||
| .help('help').argv; |
-91
| const fs = require('fs'); | ||
| const fsp = fs.promises; | ||
| const fse = require('fs-extra'); | ||
| const path = require('path'); | ||
| const open = require('open'); | ||
| const rp = require('request-promise'); | ||
| async function init(folder) { | ||
| if (!folder) folder = 'aa-project'; | ||
| const _path = path.join(process.cwd(), folder); | ||
| try { | ||
| await fsp.mkdir(_path, { recursive: true }); | ||
| await fse.copy(path.join(__dirname, 'template'), _path); | ||
| console.log('Initialization Successful. Execute:'); | ||
| if (folder !== '.') console.log('cd ' + folder); | ||
| console.log('yarn'); | ||
| console.log('If you want to work with a testnet network, rename .env.testnet file to .env'); | ||
| console.log('-------------------------'); | ||
| } | ||
| catch (e) { | ||
| console.error(e); | ||
| } | ||
| } | ||
| async function validate(path) { | ||
| if (!path) return console.error('Path not found. Please use command: aagent validate file.oscript'); | ||
| try { | ||
| await fsp.access(path, fs.F_OK); | ||
| } | ||
| catch (err) { | ||
| console.error('file not found. Please use command: aagent validate file.oscript') | ||
| } | ||
| let data; | ||
| try { | ||
| data = await fsp.readFile(path); | ||
| } | ||
| catch (err) { | ||
| return console.error(err); | ||
| } | ||
| const parse_ojson = require('ocore/formula/parse_ojson'); | ||
| const aa_validation = require('ocore/aa_validation.js'); | ||
| return await new Promise(resolve => { | ||
| parse_ojson.parse(data.toString(), function (err, arrDefinition) { | ||
| if (err) { | ||
| console.error(err); | ||
| return resolve({ err }); | ||
| } | ||
| aa_validation.validateAADefinition(arrDefinition, function (err) { | ||
| if (err) { | ||
| console.error(err); | ||
| return resolve({ err }); | ||
| } | ||
| console.log('validate: ok'); | ||
| return resolve({ aa: data.toString() }); | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| async function deploy(path, testnet) { | ||
| const result = await validate(path); | ||
| if (!result.err) { | ||
| const program = testnet ? 'obyte-tn' : 'obyte'; | ||
| let url = program + ':data?app=definition&definition=' + encodeURIComponent(result.aa); | ||
| if (url.length > 2000) { | ||
| console.log('upload to server...'); | ||
| const response = await rp({ | ||
| method: 'POST', | ||
| uri: 'https://olabs.org/aa/add', | ||
| body: { | ||
| aa: result.aa | ||
| }, | ||
| json: true | ||
| }); | ||
| if (response.err) { | ||
| return console.error(response.err); | ||
| } | ||
| url = program + ':data?app=definition&definition=' + response.url; | ||
| } | ||
| await open(url); | ||
| } | ||
| } | ||
| module.exports = { | ||
| init, | ||
| validate, | ||
| deploy | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| exports.bLight = true; | ||
| exports.storage = 'sqlite'; | ||
| exports.hub = 'obyte.org/bb-test'; | ||
| exports.deviceName = 'aagent_demo'; | ||
| exports.permanent_pairing_secret = '*'; | ||
| exports.KEYS_FILENAME = 'keys.json'; |
| require('headless-obyte'); | ||
| const eventBus = require('ocore/event_bus'); | ||
| const { AA } = require('aagent.js-lib'); | ||
| eventBus.on('headless_wallet_ready', () => { | ||
| const aa = new AA('CI7TYDWQXJNP7IW5UJHHMO6FBULJHTDH'); | ||
| aa.events.on('new_request', (request) => { | ||
| console.error('new request', request); | ||
| }); | ||
| aa.events.on('new_response', (err, response, vars) => { | ||
| console.error('new response', response, vars); | ||
| }); | ||
| }); |
| { | ||
| "name": "aagent_demo", | ||
| "version": "0.0.1", | ||
| "scripts": { | ||
| "test": "ava" | ||
| }, | ||
| "dependencies": { | ||
| "aa-testkit": "https://github.com/valyakin/aa-testkit", | ||
| "headless-obyte": "https://github.com/byteball/headless-obyte", | ||
| "aagent.js-lib": "^0.0.1" | ||
| }, | ||
| "devDependencies": { | ||
| "ava": "^3.8.2" | ||
| }, | ||
| "ava": { | ||
| "files": [ | ||
| "test/*.js" | ||
| ], | ||
| "failFast": true, | ||
| "serial": true | ||
| }, | ||
| "license": "MIT" | ||
| } |
| const { Testkit } = require('aa-testkit'); | ||
| const { Network } = Testkit(); | ||
| const { Utils } = require('aa-testkit/main'); | ||
| const fsp = require('fs').promises; | ||
| const path = require('path'); | ||
| const test = require('ava'); | ||
| const isValidAddress = Utils.isValidAddress; | ||
| const isValidUnit = function (unit) { | ||
| return Utils.isValidBase64(unit, 44) && unit.endsWith('=') | ||
| } | ||
| const pathToAA = path.join(__dirname, '..', 'aa.oscript'); | ||
| const self = {}; | ||
| test.before(async t => { | ||
| self.aa = (await fsp.readFile(pathToAA)).toString(); | ||
| self.network = await Network.create().run() | ||
| }) | ||
| test('init', async t => { | ||
| const network = self.network | ||
| const genesis = await network.getGenesisNode().ready() | ||
| const deployer = await network.newHeadlessWallet().ready() | ||
| const deployerAddress = await deployer.getAddress() | ||
| const wallet = await network.newHeadlessWallet().ready() | ||
| const walletAddress = await wallet.getAddress() | ||
| const { unit: u1 } = await genesis.sendBytes({ toAddress: deployerAddress, amount: 100000000 }) | ||
| t.true(isValidUnit(u1)); | ||
| await network.witnessUntilStable(u1); | ||
| const { unit: u2 } = await genesis.sendBytes({ toAddress: walletAddress, amount: 100000000 }) | ||
| t.true(isValidUnit(u2)); | ||
| await network.witnessUntilStable(u2); | ||
| self.deployer = deployer; | ||
| self.deployerAddress = deployerAddress; | ||
| self.wallet = wallet; | ||
| self.walletAddress = walletAddress; | ||
| }) | ||
| test('deploy aa', async t => { | ||
| const { address: aa_address, unit: aa_unit } = await self.deployer.deployAgent(self.aa); | ||
| t.true(isValidAddress(aa_address)); | ||
| t.true(isValidUnit(aa_unit)) | ||
| await self.network.witnessUntilStable(aa_unit); | ||
| self.aa_address = aa_address; | ||
| }); | ||
| test('set var', async t => { | ||
| const { unit, error } = await self.wallet.triggerAaWithData({ | ||
| toAddress: self.aa_address, | ||
| amount: 10000, | ||
| data: { | ||
| key: 'test_key', | ||
| value: 'test_value' | ||
| }, | ||
| }); | ||
| t.falsy(error); | ||
| t.true(isValidUnit(unit)); | ||
| await self.network.witnessUntilStable(unit); | ||
| const { response } = await self.network.getAaResponseToUnit(unit); | ||
| t.is(response.response.responseVars.status, 'ok'); | ||
| }); | ||
| test('get var', async t => { | ||
| const { vars } = await self.deployer.readAAStateVars(self.aa_address); | ||
| t.is(vars['test_key'], 'test_value'); | ||
| }); | ||
HTTP dependency
Supply chain riskContains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Git dependency
Supply chain riskContains a dependency which resolves to a remote git URL. Dependencies fetched from git URLs are not immutable and can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
10878
23.49%1
-85.71%191
365.85%0
-100%5
-50%154
-22.61%2
Infinity%+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed