{"id":18757733,"url":"https://github.com/libotony/sharp-example-vip180","last_synced_at":"2025-10-23T18:16:07.567Z","repository":{"id":47972507,"uuid":"206029141","full_name":"libotony/sharp-example-vip180","owner":"libotony","description":"Example project uses sharp/connex compile and test contracts","archived":false,"fork":false,"pushed_at":"2022-12-30T18:33:54.000Z","size":105,"stargazers_count":0,"open_issues_count":6,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-19T08:05:15.109Z","etag":null,"topics":["connex","contract","sharp","test","vechian"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/libotony.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-03T08:36:06.000Z","updated_at":"2023-09-11T03:18:34.000Z","dependencies_parsed_at":"2023-01-31T13:10:11.507Z","dependency_job_id":null,"html_url":"https://github.com/libotony/sharp-example-vip180","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/libotony/sharp-example-vip180","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libotony%2Fsharp-example-vip180","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libotony%2Fsharp-example-vip180/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libotony%2Fsharp-example-vip180/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libotony%2Fsharp-example-vip180/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/libotony","download_url":"https://codeload.github.com/libotony/sharp-example-vip180/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libotony%2Fsharp-example-vip180/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275665195,"owners_count":25506158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-17T02:00:09.119Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["connex","contract","sharp","test","vechian"],"created_at":"2024-11-07T17:44:26.032Z","updated_at":"2025-09-17T21:12:27.312Z","avatar_url":"https://github.com/libotony.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sharp example project\n\nCompile/Test/Deploy/Interact with contracts by *CONNEX*.\n\n## Overview\n\n+ Write contract code in your favorite editor(VSCode is mine)\n+ Compile contracts to meta by `sharp-cli compile`\n+ Test contracts in solo by `sharp-cli test [npm task name]`\n+ Deploy/Interact with contract by scripts by `sharp-cli exec [path to script]`\n\n## Project structure\n\n``` shell\n.\n├── contracts     # contracts directory\n│   ...\n│   └── my-token.sol\n├── output        # compiled contracts meta\n├── package.json\n├── scripts       # custom scripts\n...\n└── tests         # tests\n```\n\n## Bootstrap\n\n``` shell\nnpm i @libotony/sharp @libotony/sharp-cli --save\n```\n\n## Setup compiler\n\nConfiguration of sharp is located in `package.json`, under the namespace of `sharp`. For the complete guide of configuration, check [sharp-cli](https://github.com/libotony/sharp-cli). In this project we just need to specify the files need to to be compiled in `sharp.contracts`. \n\n``` javascript\n// package.json\n{  \n    \"sharp\": {\n        \"contracts_directory\": \"contracts\",\n        \"contracts\": [\n            \"my-token.sol\"\n        ],\n        \"build_directory\": \"output\",\n        \"solc\": {\n            ... // solidity compiler options\n        }\n    }\n}\n```\n\n## NPM scripts\n\n### Compile contract\n\nJust run the following command, `sharp-cli` will read configurations from `package.json` and compile the contracts.\n\n``` shell\nnpm run compile\n```\n\n### Run tests\n\n``` shell\nnpm run test/npm test/npm t\n```\n\n### Deploy contract\n\n`sharp-cli exec [file]` will create a running environment for user script, it is useful for developers deploying contracts or running customized scripts. In this project I made an example of deploying the contract.\n\n``` shell\nnpm run deploy\n```\n\n## Write test suites\n\nCommand `sharp-cli test [task]` will start a solo node in the background and then start a npm task which is aiming to run tests. In the project, I used the well know framework `mocha`.\n\n### 0x00 - setup connex\n\nWe need to setup `connex` first, to run the tests `sharp-cli` will start a solo node in the background and set the environment variable `THOR_REST` for us to initiate connex. See [connex-loader](./tests/connex-loader.ts) for the detail.\n\n``` typescript\nimport { Framework } from '@vechain/connex-framework'\nimport { Driver, SimpleNet, SimpleWallet } from '@vechain/connex.driver-nodejs'\n\nconst wallet = new SimpleWallet()\n// setup wallets here\n\nconst genesis = {...solo genesis}\n\nconst net = new SimpleNet(process.env.THOR_REST)\nconst driver = new Driver(net, genesis, undefined, wallet)\nconst connex = new Framework(driver)\n\n```\n\n### 0x01 - deploy contract\n\nIn this part, we will need [sharp](https://github.com/libotony/sharp) to get tests written. First we need `ContractMeta` to manage contract meta info.\n\n``` typescript\nimport { ContractMeta } from 'sharp'\n\nconst myTokenContract = require('../output/MyToken.json')\nconst myToken = new ContractMeta(myTokenContract.abi, myTokenContract.bytecode)\n\n// Get the ABI description of method 'balanceOf'\nconst abi0 = myToken.ABI('balanceOf')\n// Get the ABI description of event 'Transfer'\nconst abi1 = myToken.ABI('Transfer', 'event')\n\n//Build the deploy clause\nconst clause = contract\n    .deploy()\n    .value(100)             //100wei as endowment for contract creation\n    .asClause(arg0, arg1)   //args for constructor\n\n/* For my-token */\nconst { txid } = await vendor\n        .signer(addrOne) // specify the signer, it will get the total supply based on the contract logic\n        .sign('tx')\n        .request([myToken.deploy().asClause()])\n```\n\n### 0x02 - wait for receipt\n\nAfter send the transaction, we need to wait for the transaction to be packed.\n\n``` typescript\nimport { Awaiter } from 'sharp'\n\nconst receipt = await Awaiter.receipt(thor.transaction(txid), thor.ticker())\n```\n\n### 0x03 - the assertion of receipt\n\nWe should assert the emitted contract address, revert status, event log account, event log emitted in the constructor.\n\n``` typescript\nassert.isFalse(receipt.reverted, 'Should not be reverted')\nassert.equal(receipt.outputs[0].events.length, 2, 'Clause#0 should emit two events')\n// output#0 should have contractAddress emitted\nassert.isTrue(!!receipt.outputs[0].contractAddress)\n\nAssertion\n    // abi of the event\n    .event(myToken.ABI('Transfer', 'event'))\n    // the event log should be emitted by the contract\n    .by(address)\n    // mint from address 0, total supply is 1 billion\n    .logs(zeroAddress, addrOne, toWei(1e9))\n    // event located at output#0.event#1\n    // first event of deploy clause is emitted from prototype\n    .equal(receipt.outputs[0].events[1])\n```\n\n### 0x04 -  the assertion of contract call\n\nFirst read the total supply of the token:\n\n``` typescript\nconst ret = await thor.account(address)\n    .method(myToken.ABI('totalSupply'))\n    .call()\n\nAssertion\n    .method(myToken.ABI('totalSupply'))\n    // calling method should return total supply of 1 billion\n    .outputs(toWei(1e9))\n    .equal(ret)\n```\n\n### 0x05 - a method would change the state\n\nCalling a method which will change the state will not `change the statue` but you will `get the output of this action`. And you will get the output immediately without waiting for the nodes pack it in to the block.\n\n``` typescript\nconst ret = await thor.account(address)\n    .method(myToken.ABI('transfer'))\n    .caller(addrOne)\n    .call(addrTwo, toWei(100))\n\nassert.isFalse(ret.reverted, 'Should not be reverted')\nassert.equal(ret.events.length, 1, 'Output should emit one event')\nAssertion\n    .event(myToken.ABI('Transfer', 'event'))\n    .by(address)\n    .logs(addrOne, addrTwo, toWei(100))\n    .equal(receipt.events[0])\n```\n\nBut I want to read the state after this call, then we'll send the tx and read the state.\n\n``` typescript\n/* Send the transaction */\nconst { txid } = await vendor.sign('tx')\n    .signer(addrOne)\n    .request([\n        thor.account(address)\n            .method(myToken.ABI('transfer'))\n            .asClause(addrTwo, toWei(100))\n    ])\n\nconst receipt = await Awaiter.receipt(thor.transaction(txid), thor.ticker())\n\nassert.isFalse(receipt.reverted, 'Should not be reverted')\nassert.equal(receipt.outputs[0].events.length, 1, 'Clause#0 should emit one event')\nAssertion\n.event(myToken.ABI('Transfer', 'event'))\n.by(address)\n.logs(addrOne, addrTwo, toWei(100))\n.equal(receipt.outputs[0].events[0])\n\n/* Check addrOne balance */\nconst ret = await thor.account(address)\n    .method(myToken.ABI('balanceOf'))\n    .call(addrOne)\n\nAssertion\n    .method(myToken.ABI('balanceOf'))\n    .outputs(toWei(1e9 - 100))\n    .equal(ret)\n\n/* Check addrTwo balance */\nconst ret = await thor.account(address)\n    .method(myToken.ABI('balanceOf'))\n    .call(addrTwo)\n\nAssertion\n    .method(myToken.ABI('balanceOf'))\n    .outputs(toWei(100))\n    .equal(ret)\n```\n\n### 0x06 - revert message assertion\n\nIn the early age of writing contracts, we even don't which part revert of a method failed. Luckily we got revert after that.\n\n``` typescript\nconst ret = await thor.account(address)\n    .method(myToken.ABI('transfer'))\n    .caller(addrOne)\n    .call(zeroAddress, toWei(100))\n\nAssertion\n    .revert()\n    .with('VIP180: transfer to the zero address')\n    .equal(ret)\n```\n\n### 0x07 - VET transfer assertion\n\n``` typescript\nconst { txid } = await vendor.sign('tx')\n    .signer(addrOne)\n    .request([{\n        to: addrTwo,\n        value: toWei(100)\n    }])\n\nconst receipt = await Awaiter.receipt(thor.transaction(txid), thor.ticker())\n\nassert.isFalse(receipt.reverted, 'Should not be reverted')\nassert.equal(receipt.outputs[0].transfers.length, 1, 'Clause#0 should emit one transfer log')\nAssertion\n    .transfer()\n    .logs(addrOne, addrTwo, toWei(100))\n    .equal(receipt.outputs[0].transfers[0])\n```\n\n### 0x98 - setup NPM script\n\nSetting up the npm task is just the same as running tests of JS/TS project. The only difference is you need set the `test` to `sharp-cli test [npm task]`.\n\n``` javascript\n// package.json\n{  \n    \"scripts\": {\n        \"test\": \"sharp-cli test sharp\",\n        \"sharp\": \"mocha './tests/my-token.test.ts'\",\n    }\n}\n```\n\nIn this project we write test codes in typescript, so we need require the register for TS.\n\n``` javascript\n{\n    \"sharp\": \"mocha --require ts-node/register './tests/my-token.test.ts'\",\n}\n```\n\nYou may find out mocha will not exist after all tests are done, simply specify `--exit` to force mocha to quit after tests complete.\n\n\n``` javascript\n{\n    \"sharp\": \"mocha --require ts-node/register --exit './tests/my-token.test.ts'\",\n}\n```\n\nThen, `npm test` will work as we expected.\n\n### 0x99\n\nThe full detailed contract tests are in [tests](./tests/) folder.\n\n## Write user scripts\n\n`sharp-cli exec [file]` will expose `connex` and `wallet` in the global context of node which will make developers feel like executing the script in the [sync](https://env.vechain.org).\n\nFor the script, `sharp` expects it export a function as the default export:\n\n``` typescript\n// CommonJS\nmodule.exports = function async(){\n\n}\n\n// ECMAScript module\nconst main = async ()=\u003e{\n\n}\nexport default main\n```\n\nHere we write a script deploying the contract as an example:\n\n``` typescript\n// Import the script typings of extended global context, only necessary in typescript\nimport 'sharp-cli/script'\nimport { ContractMeta, Awaiter } from 'sharp'\n\nconst myTokenContract = require('../output/MyToken.json')\nconst myToken = new ContractMeta(myTokenContract.abi, myTokenContract.bytecode)\n\nconst thor = global.connex.thor\nconst vendor = global.connex.vendor\nconst wallet = global.wallet\n\n// Set up wallets, the private key is sensitive information, you may need to get from environment\n// wallet.import(process.env['ACC_PRIV'])\nwallet.import('...')\n\nconst main = async () =\u003e {\n\n    const { txid } = await vendor\n        .sign('tx')\n        .request([myToken.deploy().asClause()])\n\n    console.log(`tx sent: ${txid}, waiting receipt......`)\n    const receipt = await Awaiter.receipt(thor.transaction(txid), thor.ticker())\n    if (receipt.reverted) {\n        console.log('Failed to deploy contract')\n    } else {\n        console.log('Contract deployed at ' + receipt.outputs[0].contractAddress)\n    }\n\n}\n```\n\nThen setup the script in NPM script:\n\n``` javascript\n// package.json\n{  \n    \"scripts\": {\n        \"deploy\": \"sharp-cli exec scripts/deploy-my-token.ts\"\n    }\n}\n```\n\nAdd the register of TS:\n\n``` javascript\n// package.json\n{  \n    \"deploy\": \"sharp-cli exec scripts/deploy-my-token.ts --require ts-node/register\"\n}\n```\n\n## CI - Travis\n\nsee [.travis.yml](./.travis.yml)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibotony%2Fsharp-example-vip180","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flibotony%2Fsharp-example-vip180","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibotony%2Fsharp-example-vip180/lists"}