Ticker is a concept that describes chain increment, when there is a new block added to the chain, ticker will be triggered. This API will create a ticker which has a function that creates a Promise that will resolve when a new block is truly added, please be advised that it never rejects.
Returns Thor.Ticker
next - (): Promise<Thor.Status['head']>: Call next will create a promise that resolves with the summary of head block when there is a new block added
constticker=connex.thor.ticker()ticker.next().then((head)=>{console.log(head)})// After a few seconds> {"id":"0x00379f79ce016975dab2aa6ee21669b6ad4f4aa3fbb1ef1dfb151c52e13a8437","number":3645305,"parentID":"0x00379f781a0035250669e6f5e5170b8cb384decbbb6a83917f823d920de5eed1","timestamp":1566874740,"txsFeatures":1,"gasLimit":16000000}
Account Visitor
Account visitor is a bunch of APIs to get account details and interact with account methods.
Given the ABI of a contract, a Thor.Method object can be created to simulate a contract all without altering the contract state as well as pack a method with arguments to a clause that is ready to be signed.
Decoded will be present only the ABI definition is provided
// Simulate get name from a VIP-180 compatible contract// Solidity: function name() public pure returns(string)const nameABI = {"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"}
constnameMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(nameABI)nameMethod.call().then(output=>{console.log(output)})>{"data":"0x0000000000000000000000...","events": [],"transfers": [],"gasUsed":605,"reverted":false,"vmError":"","decoded": {"0":"VeThor" }}// Simulate the VIP-180 transfer 1 wei token from Alex to Bob// Solidity: function transfer(address _to, uint256 _amount) public returns(bool success)const transferABI = {"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}
consttransferMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(transferABI)// Set the args for simulate calltransferMethod.caller('0x7567d83b7b8d80addcb281a71d54fc7b3364ffed') // Bob's address.gas(100000) // Max gas for simulate .gasPrice('1000000000000000') // 1 VeThor can buy 1000 gas// Alice's address and amount in weitransferMethod.call('0xd3ae78222beadb038203be21ed5ce7c9b1bff602',1).then(output=>{console.log(output)})>{"data":"0x0000000000000000000000000000000000000000000000000000000000000001","events": [ {"address":"0x0000000000000000000000000000456e65726779","topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007567d83b7b8d80addcb281a71d54fc7b3364ffed","0x000000000000000000000000d3ae78222beadb038203be21ed5ce7c9b1bff602" ],"data":"0x0000000000000000000000000000000000000000000000000000000000000001" } ],"transfers": [],"gasUsed":13326,"reverted":false,"vmError":"","decoded": {"0":true,"success":true }}// Simulate EnergyStation convertForEnergy call// Solidity: function convertForEnergy(uint256 _minReturn) public payableconst convertForEnergyABI = {"constant":false,"inputs":[{"name":"_minReturn","type":"uint256"}],"name":"convertForEnergy","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"}
const convertForEnergyMethod = connex.thor.account('0xd015d91b42bed5feaf242082b11b83b431abbf4f').method(convertForEnergyABI)
// Set value, leave other arguments unsetconvertForEnergyMethod.value('1000000000000000000') // 1e18 wei// minReturn in wei(1e16 wei)convertForEnergyMethod.call('10000000000000000').then(output=>{console.log(output)})>...
Caching a Contract Call
Quote
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
Caching method calls would help developers to speed up their applications. Addresses are ideal to be the conditions of the cache invalidation because they are building states in smart contracts. We recommend developers use this caching mechanism carefully since it is primitive.
Method.cache(hints: Array<string>): this
Parameters
hints - Array<string>: Turn on caching for the method and set the condition of cache invalidation.
After turning cache on, connex will check everything on the blockchain that can be treated as address(included but not limited to):
Block.Signer
Block.Beneficiary
Transaction.Signer
Receipt.GasPayer
Receipt.Output.Event.Address
Receipt.Output.Event.ContractAddress
Receipt.Output.Event.Topics
Receipt.Output.Transfer.Sender
Receipt.Output.Transfer.Recipient
Once any address in the set is observed by connex, the cache would be expired.
// Caching for method name, return value should never expire// Solidity: function name() public pure returns(string)const nameABI = {"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"}
constnameMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(nameABI)nameMethod.cache([]) // Set this method to never expirenameMethod.call().then(output=>{console.log(output)}) // This will hit cache forever// Caching for method balanceOf, for my addresses// Solidity function balanceOf(address _owner) public view returns(uint256 balance) const balanceOfABI = {"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}
constbalanceOfMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(balanceOfABI)// Set this method to expire when my account being seenbalanceOfMethod.cache(['0x7567d83b7b8d80addcb281a71d54fc7b3364ffed'])// Get balance of my account, we will get cached result on most blocks// Event Transfer(_from = '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', ....) would make cache expiredbalanceOfMethod.call('0x7567d83b7b8d80addcb281a71d54fc7b3364ffed').then(output=>{console.log(output)})// Caching a DEX market's vet balance// Solidity: function vetVirtualBalance() public returns(bool uint104)const vetBalanceABI = {"constant":true,"inputs":[],"name":"vetVirtualBalance","outputs":[{"name":"","type":"uint104"}],"payable":false,"stateMutability":"view","type":"function"}
constvetBalanceMethod=connex.thor.account('0xD015D91B42BEd5FeaF242082b11B83B431abBf4f').method(vetBalanceABI)// Set this method to expire when the contract address being seen// Why? Because I am the developer of EnergyStation and I know the detail of the contract// vetVirtualBalance changes when there is any conversion executed and every conversion would trigger an event// and every event's output will contain contractAddress, so I set the contractAddress to the conditionvetBalanceMethod.cache(['0xD015D91B42BEd5FeaF242082b11B83B431abBf4f'])vetBalanceMethod.call('0xD015D91B42BEd5FeaF242082b11B83B431abBf4f').then(output=>{console.log(output)})// This will get the vetVirtualBalance efficiently
// Perform a VIP-180 transfer 1 wei token from Alex to Bob// Solidity: function transfer(address _to, uint256 _amount) public returns(bool success)const transferABI = {"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}
consttransferMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(transferABI)transferMethod// Bob's address and amount in wei.transact('0xd3ae78222beadb038203be21ed5ce7c9b1bff602',1).comment('transfer 1 wei to Alice').request().then(result=>{console.log(result) })>{"signer":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","txId":"0x4e9a7eec33ef6cfff8ff5589211a94070a0284df17c2ead6267f1913169bd340"}// Converts 1 VET to VeThor// Solidity: function convertForEnergy(uint256 _minReturn) public payableconst convertForEnergyABI = {"constant":false,"inputs":[{"name":"_minReturn","type":"uint256"}],"name":"convertForEnergy","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"}
const convertForEnergyMethod = connex.thor.account('0x0000000000000000000000000000456E65726779').method(convertForEnergyABI)
convertForEnergyMethod.value('1000000000000000000') // Set value to 1e18.transact('10000000000000000') // minReturn in wei(1e16 wei).comment('Convert 1 VET to VeThor').request().then(result=>{console.log(result) })>{"signer":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","txId":"0x4e9a7eec33ef6cfff8ff5589211a94070a0284df17c2ead6267f1913169bd340"}
// Convert 1 VeThor to VET, which needs to perform two action approve VeThor and convertForVETconstdex='0xD015D91B42BEd5FeaF242082b11B83B431abBf4f'const approveABI = {"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}
constapproveMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(approveABI)const convertForVetABI= {"constant":false,"inputs":[{"name":"_sellAmount","type":"uint256"},{"name":"_minReturn","type":"uint256"}],"name":"convertForVET","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}
constconvertForVetMethod=connex.thor.account(dex).method(convertForVetABI)// approve dex to move my 1e18 wei VeThorconstc1=approveMethod.asClause(dex,'1000000000000000000')// convert 1e18 wei VeThor to VET, minimum return at 1e16 wei VETconstc2=convertForVetMethod.asClause('1000000000000000000','10000000000000000')connex.vendor.sign('tx', [c1, c2]).comment('convert 1 VeThor to VET').request().then(result=>{console.log(result) })>{"to":"0x0000000000000000000000000000456E65726779","value":"0","data":"0xa9059cb......"}
Contract Event
Given the ABI of a contract, we can create a Thor.Event object that will be able to filter contracts events with arguments or pack the arguments to criteria for assembling combined filters.
indexed - object: Indexed arguments defined in event ABI that needs to be filtered, the items in the object will be combined with AND operator: e.g. {"ConA": "A", "ConB": "B"} is 'ConA=A AND ConB=B'
Returns Thor.Filter.Criteria
// Solidity: event Transfer(address indexed _from, address indexed _to, uint256 _value)const transferEventABI = {"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}
consttransferEvent=connex.thor.account('0x0000000000000000000000000000456E65726779').event(transferEventABI)// Pack into criteria filters events that the '_to' is Bob's addressconstcriteria=transferEvent.asCriteria({ _to:'0xd3ae78222beadb038203be21ed5ce7c9b1bff602'})console.log(criteria)>{"address":"0x0000000000000000000000000000456E65726779","topic0":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","topic2":"0x000000000000000000000000d3ae78222beadb038203be21ed5ce7c9b1bff602"}// Next you can combine different criteria together and put them into filter
Create a filter
Event.filter(indexed: object): Thor.Filter
Parameters
indexed - Array<object>: Array of filter conditions of indexed arguments, the items in the array will be combined by OR operator to filter the events: e.g. [{"ConA": "A"}, {"ConB": "B", "ConC": "C"}] is 'ConA=A OR (ConB=B AND ConC=C)'
// Solidity: event Transfer(address indexed _from, address indexed _to, uint256 _value)const transferEventABI = {"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}
consttransferEvent=connex.thor.account('0x0000000000000000000000000000456E65726779').event(transferEventABI)// Filter the events whether '_to' is Bob's address or '_from' is Alice's addressconstfilter=transferEvent.filter([{ _to:'0xd3ae78222beadb038203be21ed5ce7c9b1bff602'},{ _from:'0x733b7269443c70de16bbf9b0615307884bcc5636'}])// Next you can call the methods of Thor.Filter
Decoded will be present only the ABI definition is provided
// Solidity: event Transfer(address indexed _from, address indexed _to, uint256 _value)const transferEventABI = {"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}
consttransferEvent=connex.thor.account('0x0000000000000000000000000000456E65726779').event(transferEventABI)// Create a filter from eventABIconstfilter=transferEvent.filter([{ _to:'0xd3ae78222beadb038203be21ed5ce7c9b1bff602'}])// Apply the filter, get the first onefilter.apply(0,1).then(logs=>{console.log(logs)})>[ {"address":"0x0000000000000000000000000000456e65726779","topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007567d83b7b8d80addcb281a71d54fc7b3364ffed","0x00000000000000000000000000f34f4462c0f6a6f5e76fb1b6d63f05a32ed2c6" ],"data":"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000","meta": {"blockID":"0x0000813fbe48421dfdc9400f1f4e1d67ce34256538afd1c2149c4047d72c4175","blockNumber":33087,"blockTimestamp":1530345270,"txID":"0x29b0af3ffb8eff4cc48a24ce9a800aaf4d0e92b72dbcf17ce01b14fd01af1290","txOrigin":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","clauseIndex":0 },"decoded": {"0":"0x7567D83b7b8d80ADdCb281A71d54Fc7B3364ffed","1":"0x00F34f4462c0f6a6f5E76Fb1b6D63F05A32eD2C6","2":"1000000000000000000","_from":"0x7567D83b7b8d80ADdCb281A71d54Fc7B3364ffed","_to":"0x00F34f4462c0f6a6f5E76Fb1b6D63F05A32eD2C6","_value":"1000000000000000000" } }]
Block Visitor
constblk=connex.thor.block(0)
Parameters
revision - number|string|undefined: Block number or ID to visit or leave it unset the function will get the latest block ID as the revision (As long as the revision is set, it can't be changed again)
Returns Thor.BlockVisitor
revision - number|string(readonly): Block number or ID to be visited.
Filter event and transfer logs on the blockchain. Filter often works with Connex.Thor.Account, either creates a filter from an event or packs criteria and then assembles several criteria and sets to a filter. But also there is a way of creating a filter and assembling criteria as per your need then apply it.
criteria - Array<Thor.Filter.Criteria>: Criteria set for the filter, either array of Event.Criteria or an array of Transfer.Criteria, items in the criteria array will be combined by OR operator to filter the events: e.g. [{"ConA": "A"}, {"ConB": "B", "ConC": "C"}] is 'ConA=A OR (ConB=B AND ConC=C)'
Thor.Filter.Criteria:
Filters support two different types of log: event and transfer so there are two types of Thor.Filter.Criteria.
Thor.Filter.Event.Criteria:
address - string(optional): The address to get logs from
topic0 - string(optional): Topic0 to match
topic1 - string(optional): Topic1 to match
topic2 - string(optional): Topic2 to match
topic3 - string(optional): Topic3 to match
topic4 - string(optional): Topic4 to match
Thor.Filter.Transfer.Criteria:
txOrigin - string(optional): Signer address of tx
sender - string(optional): VET sender address
recipient - string(optional): VET recipient address
Returns Thor.Filter
order - (order: 'asc'|'desc'): this: Set the order for filter
range - (range: Thor.Filter.Range): this: Set the range for the filter
apply - (offset: number, limit: number): Promise<Thor.Filter.Result>: Apply the filter and get the result
connex.thor.filter('event',[// Matches VIP-180 Transfer from '0xd3ae78222beadb038203be21ed5ce7c9b1bff602' {"address":"0x0000000000000000000000000000456E65726779","topic0":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","topic1":"0x000000000000000000000000d3ae78222beadb038203be21ed5ce7c9b1bff602" },// Matches VIP-180 Transfer to '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed' {"address":"0x0000000000000000000000000000456E65726779","topic0":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","topic2":"0x0000000000000000000000007567d83b7b8d80addcb281a71d54fc7b3364ffed" }])// Next you can set other options or executes the filter
Set filter range
Parameters
Thor.Filter.Range:
unit - 'block'|'time': Range unit, can be filtered by block number or timestamp in second
from - Number: Start point in unit
to - Number: Stop point in unit
Returns this
constfilter=connex.thor.filter('transfer')// Set the filter range as block 0 to block 100filter.range({ unit:'block', from:0, to:100})// Next you can set other options or executes the filter
Execute the filter
Parameters
offset - Number: Start cursor in the result
limit - Number: Constrain the number of result returned
// Solidity: event Transfer(address indexed _from, address indexed _to, uint256 _value)const transferEventABI = {"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}
consttransferEvent=connex.thor.account('0x0000000000000000000000000000456E65726779').event(transferEventABI)// Create a filter from eventABIconstfilter=transferEvent.filter([{ _to:'0xd3ae78222beadb038203be21ed5ce7c9b1bff602'}])// Set filter optionsfilter.order('desc') // Work from the last event.range({ unit:'block', from:10000, to:40000 }) // Set the range// Apply the filter, get the first onefilter.apply(0,1).then(logs=>{console.log(logs)})>[ {"address":"0x0000000000000000000000000000456e65726779","topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007567d83b7b8d80addcb281a71d54fc7b3364ffed","0x00000000000000000000000000f34f4462c0f6a6f5e76fb1b6d63f05a32ed2c6" ],"data":"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000","meta": {"blockID":"0x0000813fbe48421dfdc9400f1f4e1d67ce34256538afd1c2149c4047d72c4175","blockNumber":33087,"blockTimestamp":1530345270,"txID":"0x29b0af3ffb8eff4cc48a24ce9a800aaf4d0e92b72dbcf17ce01b14fd01af1290","txOrigin":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","clauseIndex":0 },"decoded": {"0":"0x7567D83b7b8d80ADdCb281A71d54Fc7B3364ffed","1":"0x00F34f4462c0f6a6f5E76Fb1b6D63F05A32eD2C6","2":"1000000000000000000","_from":"0x7567D83b7b8d80ADdCb281A71d54Fc7B3364ffed","_to":"0x00F34f4462c0f6a6f5E76Fb1b6D63F05A32eD2C6","_value":"1000000000000000000" } }]// Filter the transfer log that from 0x7567d83b7b8d80addcb281a71d54fc7b3364ffedconstfilter=connex.thor.filter('transfer', [{ sender:'0x7567d83b7b8d80addcb281a71d54fc7b3364ffed'}])filter.apply(0,1).then(logs=>{console.log(logs)})>[ {"sender":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","recipient":"0x3cc190d342a0d3a7365538d94adffec34f789cd0","amount":"0xde0b6b3a7640000","meta": {"blockID":"0x00005be5d2129f01cb60ef20b43208091722bf6e0086ac24745cd26698e9d93d","blockNumber":23525,"blockTimestamp":1530249650,"txID":"0xd08e959c0ae918ab72d4da162856e7a4556c576b8ae849d09dbd38e5a419e94b","txOrigin":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","clauseIndex":0 } }]
Explainer
Explainer gets what would be produced after blockchain executes a tx, without committing to the blockchain.
abi - object(optional): ABI definition of this clause, for User-Agent decoding the data
CertMessage:
purpose - 'identification' | 'agreement': Purpose of the request, identification means request the user to sign a random message to get the address and agreement means ask user to sign the agreement for using the VeChain apps
payload:
type - 'text': Payload type,only text is supported
content - string: Content of of the request
Returns Connex.Vendor.TxSigningService or Connex.Vendor.CertSigningService
Transaction Signing Service
Connex.Vendor.TxSigningService:
signer - (addr: string): this: Enforces the specified address to sign the transaction
gas - (gas: number): this: Enforces the specified number as the maximum gas that can be consumed for the transaction
dependsOn - (txid: string): this: Set another txid as dependency (Reference)
link - (url: string): this: Set the link to reveal transaction-related information, the link will be used for connex to assemble a callback url by replacing the placeholder {txid} by Transaction ID
comment - (text: string): this: Set the comment for the transaction that will be revealed to the user
delegate - (url: string, signer: string): this: Enable VIP-191 by setting the url and the fee payer's address. Wallets(Sync2) will request fee payer's signature with the url and required parameters, read this to get the detail about building a VIP-191 service
accepted - (cb: ()=>void): this: Register a callback function which will be fired once user's wallet accepted the request
request - (): Promise<Connex.Vendor.TXResponse>: Perform the request
Perform Transaction Signing Request
Returns Promise<Connex.Vendor.TXResponse>:
txid - string: Transaction identifier
signer - string: Signer address
// Prepare energy transfer clauseconst transferABI = {"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}
consttransferMethod=connex.thor.account('0x0000000000000000000000000000456E65726779').method(transferABI)// Connex author's address and amount in weiconstenergyClause=transferMethod.asClause('0xd3ae78222beadb038203be21ed5ce7c9b1bff602','1000000000000000000000')connex.vendor.sign('tx', [ { to:'0xd3ae78222beadb038203be21ed5ce7c9b1bff602', value:'100000000000000000000', data:'0x', comment:'Transfer 100 VET' }, { comment:'Transfer 1000 VeThor',...energyClause }]).signer('0x7567d83b7b8d80addcb281a71d54fc7b3364ffed') // Enforce signer.gas(200000) // Set maximum gas.link('https://connex.vecha.in/{txid}') // User will be back to the app by the url https://connex.vecha.in/0xffff.....comment('Donate 100 VET and 1000 VeThor to the author of connex').request().then(result=>{console.log(result)})>{"signer":"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed","txId":"0x4e9a7eec33ef6cfff8ff5589211a94070a0284df17c2ead6267f1913169bd340"}
The certificate is a message signing based mechanism which can easily request user's identification(address) or user to agree to your terms or agreements.
Connex.Vendor.CertSigningService:
signer - (addr: string): this: Enforces the specified address to sign the certificate
link - (url: string): this: Set the link to reveal certificate-related information, the link will be used for connex to assemble a callback url by replacing the placeholder {certid} by Certificate ID, which is computed by blake2b256(Certificate.encode(cert))
accepted - (cb: ()=>void): this: Register a callback function which will be fired once user's wallet accepted the request
request - (): Promise<Connex.Vendor.CertResponse>: Send the request
Perform Certificate Signing Request
Returns Promise<Connex.Vendor.CertResponse>:
annex:
domain - string: Domain of the VeChain apps
timestamp - number: Head block timestamp when user accepts the request
signer - string: Signer address
signature - string: Signature
connex.vendor.sign('cert',{ purpose:'identification', payload: { type:'text', content:'random generated string' }}).link('https://connex.vecha.in/{certid}') // User will be back to the app by the url https://connex.vecha.in/0xffff.....request().then(result=>{console.log(result)})>{"annex": {"domain":"vechain.github.io","timestamp":1545826680,"signer":"0xf6e78a5584c06e2dec5c675d357f050a5402a730" }, "signature": "0x30db85935f620f7c506462f3ec7552479a56db8cbd0c2a3f295a92ad4e2f37ae2d2b2a2a212c45c43eeaf6e5caa8b3348038c01192ed226d12a118f204c6729200"
}// Ask user to sign the agreementconnex.vendor.sign('cert',{ purpose:'agreement', payload: { type:'text', content:'agreement' }}).request().then(result=>{