Settings

Blockchain
Network
Unit
Language
Theme
Sound New Block

Transaction

15780ffeefb682cb6cea9c07d9efb295abb60d775d275fb5083a9eb8b948b482
Timestamp (utc)
2020-10-07 23:03:43
Fee Paid
0.00014830 BSV
(
0.00023513 BSV
-
0.00008683 BSV
)
Fee Rate
500 sat/KB
Version
1
Confirmations
339,726
Size Stats
29,659 B

4 Outputs

Total Output:
0.00008683 BSV
  • jrun b1b605103eM¶r{"in":0,"ref":["native://Jig"],"out":["8ce73ce86f01985dbebdefeb8daa10262cd684679e5be9fa1f7d81f369f8ac1d","fbbb229579b7f0e4f4f45fd7ab7c8cf36e664f2290620b9a63ed53624f3d2763"],"del":[],"cre":["mr9zdspVwjQBCsRQ464ct8M2wJgaL7CcaQ","mr9zdspVwjQBCsRQ464ct8M2wJgaL7CcaQ"],"exec":[{"op":"DEPLOY","data":["class TokenContract extends Jig {\n //\n // with RUN 0.6 preview version \n //\n init(timestamp, ...tokens) {\n this.classname = \"TokenContract: \";\n const function_id = this.classname + \"init(): \"; //\n // The base Token class cannot be created on its own\n\n if (Object.getPrototypeOf(this.constructor) === Jig) {\n throw new Error(function_id + 'must be extended');\n } //\n\n\n this._checkTimestamp(timestamp); // important to check here because we can't do it in static functions\n //\n // Case: Combine\n\n\n if (caller == null) {\n //called by user\n const function_id_combine = function_id + \" combine : \";\n\n try {\n // it is very important to block combine if contract is frozen/backedup/revoked to prevent honest users from mixing their coins with hacked ones\n if (this.constructor.isRevoked()) throw new Error(function_id + \"token contract has been revoked\");\n if (this.constructor.isBackedup()) throw new Error(function_id + \"token contract has been backedup\");\n if (this.constructor.isFrozen()) throw new Error(function_id + \"token contract has been frozen\"); // check frozen as last always!\n // we cannot check if this.owner got blacklisted as we are in init where owner is not yet defined, but we can check for each token.owner\n\n if (!Array.isArray(tokens) || tokens.length < 2) {\n throw new Error(function_id_combine + \"Invalid tokens to combine : \", tokens);\n } // Each token to combine must all be of this type\n\n\n if (tokens.some(token => token.constructor !== this.constructor)) {\n throw new Error(function_id_combine + \"Cannot combine different token classes\");\n } // Check for duplicate tokens in the array\n\n\n const countOf = token => tokens.reduce((count, next) => next === token ? count + 1 : count, 0);\n\n if (tokens.some(token => countOf(token) > 1)) throw new Error(function_id_combine + \"Cannot combine duplicate tokens\"); // Destroy each token, absorbing it into this one\n\n this.amount = 0;\n var oldest_mint_count = null;\n tokens.forEach(token => {\n if (this.isBlacklisted(token.owner)) throw new Error(function_id_combine + \"your address got blacklisted: \" + token.owner);\n this.amount += token.amount;\n if (oldest_mint_count == null || token.oldest_mint_count < oldest_mint_count) oldest_mint_count = token.oldest_mint_count;\n token.destroy(); // this ensures only the owner of the tokens can combine because this needs to be signed by the owner of the tokens to combine\n });\n this.action = \"combine\";\n this.timestamp = timestamp;\n this.oldest_mint_count = oldest_mint_count; // Make sure our new amount is within safe range\n\n this._checkAmount(this.amount);\n } catch (error) {\n throw new Error(function_id_combine, error);\n }\n\n return;\n } // Cases: Mint & Convert\n\n\n if (caller === this.constructor) {\n // call from static function of this class\n //\n if (caller.mintAmount) {\n // we could also check that 'tokens' array from parameters is empty\n //\n // Case: Mint\n const function_id_mint = function_id + \" mint : \";\n\n try {\n if (this.constructor.isRevoked()) throw new Error(function_id_mint + \"token contract has been revoked\");\n if (this.constructor.isBackedup()) throw new Error(function_id_mint + \"token contract has been backedup\");\n if (this.constructor.isFrozen()) throw new Error(function_id_mint + \"token contract has been frozen\"); // check frozen as last always!\n // no need to check blacklist in case of mint since we can't blacklist ourselves\n\n this._checkAmount(caller.mintAmount); // important to check here\n\n\n this.amount = caller.mintAmount;\n this.action = \"mint\"; //\n //console.log(function_id_mint+\" \\n caller.mintAmount=\"+caller.mintAmount+\"\\n timestamp=\"+timestamp+\"\\n caller=\",caller,\"\\n \"+caller)\n //\n\n this.timestamp = timestamp;\n this.oldest_mint_count = this.constructor.nonce; // TODO see if we should add +1 here ?\n //console.log(function_id_mint+\" this.oldest_mint_count = \"+this.oldest_mint_count)\n } catch (error) {\n throw new Error(function_id_mint, error);\n }\n\n return;\n } else {\n //\n // Case: Convert\n const function_id_convert = function_id + \" convert : \";\n\n try {\n if (!tokens) throw new Error(function_id_convert + \" tokens array from parameters not found \");\n if (tokens.length != 1) throw new Error(function_id_convert + \" tokens array from parameters has length \" + tokens.length + \", length should be 1\"); //\n\n if (this.constructor.isRevoked()) throw new Error(function_id_convert + \"token contract has been revoked\");\n if (this.constructor.isBackedup()) throw new Error(function_id_convert + \"token contract has been backedup\");\n if (this.constructor.isFrozen()) throw new Error(function_id_convert + \"token contract has been frozen\"); // check frozen as last always!\n //\n\n if (!this.constructor.accepted_conversions) throw new Error(function_id_convert + \"no accepted_conversions found in the token contract\"); //\n // check not blacklisted in current contract\n\n var checknotblacklisted = this._checkNotBlacklisted(this.owner);\n\n if (checknotblacklisted == false) throw new Error(function_id_convert + \"your address got blacklisted : \" + this.owner); // because the new contract copied the blacklist before the revoke_count, checking in the current contract is enough and there is no need to check blacklist from the previous contract\n //\n // remark: we don't care about entries after the revoke_count in the multlist here because we only deal with basic, raw amounts\n //\n // CHECK FOR REVOKED MINTS based on oldest_mint_count\n\n if (old_token.constructor.isRevoked()) // if previous contract was revoked\n {\n const prev_revokercontract = old_token.constructor.revokercontract;\n if (!prev_revokercontract) throw new Error(function_id_convert + \" no contract.revokercontract found in the contract of the token to convert\");\n const revoke_count = prev_revokercontract.revoke_count;\n if (old_token.oldest_mint_count >= revoke_count) throw new Error(function_id_convert + \"cannot convert revoked tokens\");\n } //\n\n\n if (!timestamp) throw function_id_convert + \"timestamp missing: \" + timestamp;\n\n this._checkTimestamp(timestamp); //\n\n\n const old_token = tokens[0]; // we already checked that there is only one token to convert given in parameters so this is safe\n //\n\n if (timestamp <= old_token.timestamp) throw new Error(function_id_convert + \"you cannot convert with a timestamp that is before the old_token's timestamp: \" + timestamp + \" <= \" + old_token.timestamp); //\n\n if (!old_token) throw new Error(function_id_convert + \"you must give an old token to convert for a new one, old_token=\" + old_token);\n if (!(old_token instanceof Jig)) throw new Error(function_id_convert + 'bad old_token type, should be a Jig');\n if (!this.constructor.accepted_conversions.includes(old_token.constructor.origin)) throw new Error(function_id_convert + \" wrong old_token origin: current token_contract.accepted_conversions doesn't include \" + old_token.constructor.origin); //\n\n if (this.owner != old_token.owner) throw new Error(function_id_convert + \"you cannot convert an old_token that you do not own\"); //\n // now we have a valid timestamp and some valid old_token that are validated as such in the token_contract & the caller is also the owner of this old_token => we can convert it\n //\n // to prevent converting ad infinitum of the old tokens, we don't mint new ones if we cannot destroy the old tokens first \n\n old_token.destroy(); // destroy the old token, this also ensure that only the old_token owner can convert (good to have a double check)\n //\n // now that the old token has been burned we can mint the new one\n\n this.action = \"convert\";\n this.amount = old_token.amount;\n this.timestamp = timestamp;\n this.oldest_mint_count = 0; //all the new converted tokens have an oldest_mint_count of 0. another solution would be to make them at old_token.oldest_mint_count and restart the new adminCounter from the value of the old one\n } catch (error) {\n throw new Error(function_id_convert, error);\n }\n\n return;\n }\n } //\n // Case: Send\n\n\n if (caller && caller.constructor === this.constructor) {\n // call from a non-static function of this class\n const function_id_send = function_id + \" send : \";\n\n try {\n if (this.constructor.isRevoked()) throw new Error(function_id_send + \"token contract has been revoked\");\n if (this.constructor.isBackedup()) throw new Error(function_id_send + \"token contract has been backedup\");\n if (this.constructor.isFrozen()) throw new Error(function_id_send + \"token contract has been frozen\"); // check frozen as last always!\n\n if (this.isBlacklisted(caller.sendSender)) throw new Error(function_id_send + \"your address got blacklisted: \" + caller.sendSender);\n if (this.isBlacklisted(caller.sendOwner)) throw new Error(function_id_send + \"the address you are trying to send to got blacklisted: \" + caller.sendOwner);\n\n this._checkAmount(caller.sendAmount); //\n\n\n this.amount = caller.sendAmount;\n this.owner = caller.sendOwner;\n this.timestamp = timestamp;\n this.sender = caller.sendSender;\n this.oldest_mint_count = caller.send_oldest_mint_count;\n this.action = \"send\";\n } catch (error) {\n throw new Error(function_id_send, error);\n }\n\n return;\n }\n } // idea: we could only allow after checking if this.nonce < 5 to add some kind of timing / action limit\n\n\n static allow_convert_from_previous_contract(previous_tokencontract) {\n const function_id = this.name + (this.tokenName ? \" (\" + this.tokenName + \")\" : \"\") + \": allow_convert_from_previous_contract(): \"; //\n\n if (this.accepted_conversions) throw new Error(function_id + \"already called this function, it can only be called once.\"); // we can't allow calling more than once otherwise a hacker could add other contracts to it\n\n this.accepted_conversions = []; // init\n //\n\n if (!(previous_tokencontract instanceof Jig)) throw new Error(function_id + \" parameter previous_tokencontract must be a Jig\");\n if (previous_tokencontract.origin = this.origin) throw new Error(function_id + \"cannot link a contract to itself\"); //\n\n if (!previous_tokencontract.revokercontract) throw new Error(function_id + \" previous_tokencontract.revokercontract not found\");\n if (!previous_tokencontract.multlist) throw new Error(function_id + \" previous_tokencontract.multlist not found\");\n if (!previous_tokencontract.blacklist) throw new Error(function_id + \" previous_tokencontract.blacklist not found\");\n if (!previous_tokencontract.mint_action_numbers) throw new Error(function_id + \" previous_tokencontract.mint_action_numbers not found\");\n if (!previous_tokencontract.mint_list) throw new Error(function_id + \" previous_tokencontract.mint_list not found\"); //\n\n const previous_isrevoked = previous_tokencontract.revokercontract.is_revoked;\n var prev_revoke_count = null;\n var validsupply_toadd = 0;\n var i = 0;\n\n if (previous_isrevoked) {\n prev_revoke_count = previous_tokencontract.revokercontract.revoke_count;\n if (!prev_revoke_count) throw new Error(function_id + \" contract was revoked but revoke_count not found in \" + previous_tokencontract.revokercontract.location); // let's calculate supply by adding all the mints before the prev_revoke_count\n\n mint_actionnums = previous_tokencontract.mint_action_numbers;\n mintlist = previous_tokencontract.mint_list;\n i = 0;\n\n while (mint_actionnums[i] < prev_revoke_count) {\n validsupply_toadd += mintlist[i];\n i += 1;\n }\n } else {\n // the previous contract was not revoked so we can just add the entire supply \n validsupply_toadd = previous_tokencontract.supply;\n }\n\n this.supply += validsupply_toadd; //\n // now add to accepted_conversions the origin of the previous_tokencontract with its prev_revoke_count\n\n this.accepted_conversions.push([previous_tokencontract.origin, prev_revoke_count]); // now we add the list of accepted_conversions from the previous_tokencontract\n\n const prev_conversions = previous_tokencontract.accepted_conversions;\n\n for (i = 0; i < prev_conversions.length; i++) {\n this.accepted_conversions.push(prev_conversions[i]);\n } //console.log(function_id+\" this.accepted_conversions = \", this.accepted_conversions)\n //\n // now copy previous mintuples & blacklist\n\n\n var prev_multlist = previous_tokencontract.multlist;\n var prev_blacklist = previous_tokencontract.blacklist; //\n // copy mintuples\n\n i = 0;\n\n while (prev_multlist.list_action_numbers[i] < prev_revoke_count) {\n var newmult = prev_multlist.list[i];\n var newtimestamp = prev_multlist.timestamps[i];\n this.multlist.publish(newmult, newtimestamp);\n i += 1;\n } //console.log(function_id+\" this.multlist.list = \", this.multlist.list)\n //\n // copy blacklist\n\n\n i = 0;\n\n while (prev_blacklist.list_action_numbers[i] < prev_revoke_count) {\n var newaddress = prev_blacklist.list[i];\n var newtimestamp = prev_blacklist.timestamps[i];\n this.blacklist.blacklist(newaddress, newtimestamp);\n i += 1;\n } //console.log(function_id+\" this.blacklist.list = \", this.blacklist.list)\n\n }\n\n static mint(amount, timestamp) {\n const function_id = this.name + (this.tokenName ? \" (\" + this.tokenName + \")\" : \"\") + \": mint(): \";\n this.mintAmount = amount; // this ensures only the class owner can mint because others cannot create class properties\n // we cannot call _checkTimestamp in a static function because it's private but make sure to check in the init()\n // same for isBlacklisted, isFrozen\n // => mais on pourrait tout mettre en static ?\n // init mint lists if not already done => this will serve to calculate the supply with the revoke_count if the contract was revoked\n\n if (!this.mint_list) this.mint_list = [];\n if (!this.mint_timestamps) this.mint_timestamps = []; // timestamps are not really necessary for revoking but why not save them too\n\n if (!this.mint_action_numbers) this.mint_action_numbers = [];\n this.mint_list.push(amount);\n this.mint_timestamps.push(timestamp);\n this.mint_action_numbers.push(this.nonce); // todo see if we should add +1 here ?\n\n const token = new this(timestamp);\n delete this.mintAmount;\n this.supply += amount;\n return token;\n }\n\n static combine(timestamp, ...tokens) {\n const function_id = this.name + (this.tokenName ? \" (\" + this.tokenName + \")\" : \"\") + \": combine(): \"; // we cannot call _checkTimestamp in a static function because it's private but make sure to check in the init()\n // same for isBlacklisted, isFrozen\n // => mais on pourrait tout mettre en static ?\n\n const token = new this(timestamp, ...tokens);\n return token;\n }\n\n static convert_tokens(timestamp, ...tokenstoconvert) {\n const function_id = this.name + (this.tokenName ? \" (\" + this.tokenName + \")\" : \"\") + \": convert_tokens(): \"; //\n // this allows exchanging old tokens for these new ones after they have been revoked or backedup\n //\n // no need to check blacklisted or timestamp or anything else here because it will be done in the call below\n // + this is a static method so it doesn't have access to this & other functions\n //\n\n var new_tokens = [];\n\n try {\n tokenstoconvert.forEach(curr_oldtoken => {\n var newtoken = new this(timestamp, curr_oldtoken);\n new_tokens.push(newtoken);\n });\n } catch (error) {\n throw new Error(function_id, error);\n }\n\n return new_tokens;\n }\n\n static reduceSupplyIfAdmin(amount) {\n const function_id = this.name + (this.tokenName ? \" (\" + this.tokenName + \")\" : \"\") + \": reduceSupplyIfAdmin(): \"; // working if you call it from destroy() with this.constructor.reduceSupplyIfAdmin(this.amount)\n\n if (!(caller instanceof this)) throw function_id + \" this function cannot be called by a user (it is static but also private in a way)\"; // checkAmount, we have to redo it step by step here since we cannot call private functions from static ones\n\n if (typeof amount !== 'number') throw new Error(function_id + 'amount is not a number : ' + amount); // using throw gives better error trace than expect()\n\n if (!Number.isInteger(amount)) throw new Error(function_id + 'amount must be an integer : ' + amount);\n if (amount <= 0) throw new Error(function_id + 'amount must be positive : ' + amount);\n if (amount > Number.MAX_SAFE_INTEGER) throw new Error(function_id + 'amount too large : ' + amount);\n if (amount > this.supply) throw function_id + \" amount too large, you are trying to reduce supply by \" + amount + \" but supply is \" + this.supply; //console.log(function_id+\" caller = \\n \"+caller+\"\\n \",caller)\n\n this.supply -= amount; // this can only be done by the class' owner so it enforces that only the admin can call this function\n } // redefining the one from extended Jig class\n\n\n destroy() {\n // BEWARE for admin YOU SHOULD RATHER USE BURN TO ATTACH A PRECISE TIMESTAMP TO THIS ACTION\n const function_id = this.classname + \"destroy(): \"; // beware destroy() should remain possible even if the contract was revoked, backedup, or frozen (to allow converting coins to new contract)\n // beware destroy applies to all coins so it doesn't decrease supply if someone that isn't the class' owner destroys his coins\n //console.log(function_id+\" caller : \"+caller+\"\\n\",caller)\n\n if (caller == null && this.owner == this.constructor.owner) {\n // caller is user and is the owner of the token's class\n // user called destroy(). we are not combining or sending tokens\n //console.log(function_id+\" destroy from admin detected : try to reduce token supply\")\n this.constructor.reduceSupplyIfAdmin(this.amount);\n }\n\n super.destroy();\n this.action = \"destroy\";\n this.amount = 0;\n }\n\n burn(timestamp, amount_to_burn) {\n // amount_to_burn is optional, if not given will burn all\n const function_id = this.classname + \"burn(): \";\n if (this.constructor.isRevoked()) throw new Error(function_id + \"token contract has been revoked\");\n if (this.constructor.isBackedup()) throw new Error(function_id + \"token contract has been backedup\");\n if (this.constructor.isFrozen()) throw new Error(function_id + \"token contract has been frozen\"); // check frozen as last always!\n\n if (this.owner != this.constructor.owner) throw function_id + \" Only \" + this.classname + \" class owner can burn\\n class owner is \" + this.constructor.owner + \" and you are \" + this.owner; //if no amount_to_burn specified, burn all\n\n amount_to_burn = typeof amount_to_burn === 'undefined' ? this.amount : amount_to_burn;\n\n this._checkAmount(amount_to_burn);\n\n if (amount_to_burn > this.amount) throw function_id + \" not enough funds. Trying to burn \" + amount_to_burn + \" & this.amount=\" + this.amount;\n if (!timestamp) throw function_id + \" timestamp missing: \" + timestamp;\n\n this._checkTimestamp(timestamp); // all checks passed\n\n\n this.timestamp = timestamp;\n this.action = \"burn\";\n this.amount_burnt = amount_to_burn;\n this.action_count = this.constructor.nonce; // TODO see if we should do +1 ?\n\n if (amount_to_burn == this.amount) {\n //this.destroy() // TODO maybe we should use that instead ?\n this.amount = 0;\n } else {\n this.amount -= amount_to_burn; // decrease amount\n } //console.log(function_id+\" state of parent class : \",this.constructor) // logging jigs for now creates bugs\n\n\n this.constructor.reduceSupplyIfAdmin(amount_to_burn);\n }\n\n send(to, timestamp, amount = this.amount) {\n const function_id = this.classname + \"send(): \";\n\n try {\n if (this.constructor.isRevoked()) throw new Error(function_id + \"token contract has been revoked\");\n if (this.constructor.isBackedup()) throw new Error(function_id + \"token contract has been backedup\");\n if (this.constructor.isFrozen()) throw new Error(function_id + \"token contract has been frozen\"); // check frozen as last always!\n\n this._checkAmount(amount);\n\n if (amount > this.amount) {\n throw new Error(function_id + \"Not enough funds\"); //, trying to send \"+amount+\" but this UTXO only has \"+this.amount)\n }\n\n this._checkTimestamp(timestamp);\n\n if (timestamp <= this.timestamp) throw function_id + \"you cannot send with a timestamp that is before the coin to send's timestamp: \" + timestamp + \" <= \" + this.timestamp;\n if (this.isBlacklisted(this.owner)) throw function_id + \"your address got blacklisted: \" + this.owner;\n if (this.isBlacklisted(to)) throw function_id + \" the address you are trying to send to got blacklisted: \" + to;\n this.sendAmount = amount;\n this.sendOwner = to;\n this.sendSender = this.owner;\n this.send_oldest_mint_count = this.oldest_mint_count;\n const sent = new this.constructor(timestamp);\n delete this.sendAmount;\n delete this.sendOwner;\n delete this.sendSender;\n delete this.send_oldest_mint_count;\n\n if (this.amount === amount) {\n // || (this.amount - amount) <= 10) { // change token with amount below 10 with decimals=8 is useless and happens too often\n this.destroy();\n } else {\n this.amount -= amount;\n this.action = \"change\";\n }\n\n return sent;\n } catch (e) {\n throw function_id + e;\n }\n }\n\n isBlacklisted(address) {\n var blacklist = this.constructor.blacklist;\n if (!blacklist) throw function_id + \" no blacklist found\";\n return blacklist.isBlacklisted(address);\n }\n\n _checkNotBlacklisted(addresstocheck) {\n // addresstocheck can be this.owner for example\n const function_id = this.classname + \"_checkNotBlacklisted(): \";\n var blacklist = this.constructor.blacklist;\n if (!blacklist) throw new Error(function_id + \" no blacklist found\");\n if (blacklist.list.includes(addresstocheck)) throw new Error(function_id + \"address got blacklisted: \" + addresstocheck);\n return true;\n } // BEWARE WE CAN FREEZE AND STILL BACKUP OR REVOKE AFTER\n\n\n static isFrozen() {\n // static version so that multlist & blacklist can also call it\n const function_id = this.classname + \"isFrozen(): \";\n if (!this.revokercontract) throw new Error(function_id + \"no revokercontract found\");\n return this.revokercontract.is_frozen;\n }\n\n static isBackedup() {\n // static version so that multlist & blacklist can also call it\n const function_id = this.classname + \"isBackedup(): \";\n if (!this.revokercontract) throw new Error(function_id + \"no revokercontract found\");\n return this.revokercontract.is_backedup;\n }\n\n static isRevoked() {\n // static version so that multlist & blacklist can also call it\n const function_id = this.classname + \"isRevoked(): \";\n if (!this.revokercontract) throw new Error(function_id + \"no revokercontract found\");\n return this.revokercontract.is_revoked;\n }\n\n _checkRevokeState() {\n const function_id = this.classname + \"_checkRevokeState(): \";\n var revokercontract = this.constructor.revokercontract;\n if (!revokercontract) throw new Error(function_id + \" no revokercontract found\");\n if (revokercontract.is_backedup) throw new Error(function_id + \" token contract has been backedup\");\n if (revokercontract.is_revoked) throw new Error(function_id + \" token contract has been revoked\");\n if (revokercontract.is_frozen) throw new Error(function_id + \" token contract has been frozen\"); // check frozen as last\n\n return true;\n }\n\n _checkAmount(amount) {\n const function_id = this.classname + \": _checkAmount(): \";\n if (typeof amount !== 'number') throw new Error(function_id + 'amount is not a number : ' + amount); // using throw gives better error trace than expect()\n\n if (!Number.isInteger(amount)) throw new Error(function_id + 'amount must be an integer : ' + amount);\n if (amount <= 0) throw new Error(function_id + 'amount must be positive : ' + amount);\n if (amount > Number.MAX_SAFE_INTEGER) throw new Error(function_id + 'amount too large : ' + amount);\n }\n\n _checkNum(number) {\n // check that number is a positive number (but can be float)\n const function_id = this.classname + \": _checkNum(): \";\n if (typeof number !== 'number') throw function_id + 'number is not a number : ' + number;\n if (!(number > 0)) throw new Error(function_id + 'number must be positive : ' + number);\n if (number > Number.MAX_SAFE_INTEGER) throw new Error(function_id + 'number too large : ' + number);\n }\n\n _checkTimestamp(timestamp) {\n const function_id = this.classname + \": _checkTimestamp(): \";\n\n try {\n this._checkAmount(timestamp); // applies as well to timestamp\n\n } catch (e) {\n throw function_id + e;\n }\n\n if (!(timestamp > 1600939295117)) throw function_id + ': timestamp must be older than 1600939295117 : ' + timestamp; // make sure the timestamp here is in ms !!\n }\n\n}",{"decimals":8,"deps":{"Jig":{"$jig":0},"expect":{"$jig":2}},"icon":{"emoji":null},"sealed":false,"supply":0,"symbol":null},"function expect(t){let e=!1;const n=t=>{if(\"object\"!=typeof t||!t)return t;try{return JSON.stringify(t)}catch(e){return t.toString()}};function r(r,o,i){if(e?r:!r)throw new Error(i||`expected value${e?\" not\":\"\"} to be ${o} but was ${n(t)}`)}function o(t,e){if(t===e)return!0;if(typeof t!=typeof e)return!1;if(\"object\"!=typeof t)return!1;if(null===t||null===e)return!1;if(Object.getPrototypeOf(t)!==Object.getPrototypeOf(e))return!1;if(Object.keys(t).length!==Object.keys(e).length)return!1;if(!Object.keys(t).every(n=>o(t[n],e[n])))return!1;if(t instanceof Set){if(t.size!==e.size)return!1;if(!o(Array.from(t.entries()),Array.from(e.entries())))return!1}if(t instanceof Map){if(t.size!==e.size)return!1;if(!o(Array.from(t.entries()),Array.from(e.entries())))return!1}return!0}function i(t,e){if(\"function\"!=typeof t)return!1;if(\"function\"!=typeof e)return!1;for(;t;)if((t=Object.getPrototypeOf(t))===e)return!0;return!1}return{get not(){return e=!e,this},toBe:(e,o)=>r(t===e,\"\"+n(e),o),toEqual:(e,i)=>r(o(t,e),\"equal to \"+n(e),i),toBeInstanceOf:(e,n)=>r(t&&t instanceof e,\"an instance of \"+(e&&e.name),n),toBeDefined:e=>r(void 0!==t,\"defined\",e),toBeNull:e=>r(null===t,\"null\",e),toBeNumber:e=>r(\"number\"==typeof t,\"a number\",e),toBeInteger:e=>r(Number.isInteger(t),\"an integer\",e),toBeLessThan:(e,n)=>r(t<e&&\"number\"==typeof t&&\"number\"==typeof e,\"less than \"+e,n),toBeLessThanOrEqualTo:(e,n)=>r(t<=e&&\"number\"==typeof t&&\"number\"==typeof e,\"less than or equal to \"+e,n),toBeGreaterThan:(e,n)=>r(t>e&&\"number\"==typeof t&&\"number\"==typeof e,\"greater than \"+e,n),toBeGreaterThanOrEqualTo:(e,n)=>r(t>=e&&\"number\"==typeof t&&\"number\"==typeof e,\"greater than or equal to \"+e,n),toBeBoolean:e=>r(\"boolean\"==typeof t,\"a boolean\",e),toBeString:e=>r(\"string\"==typeof t,\"a string\",e),toBeObject:e=>r(t&&\"object\"==typeof t,\"an object\",e),toBeArray:e=>r(Array.isArray(t),\"an array\",e),toBeSet:e=>r(t instanceof Set,\"a set\",e),toBeMap:e=>r(t instanceof Map,\"a map\",e),toBeUint8Array:e=>r(t instanceof Uint8Array,\"a uint8array\",e),toBeClass:e=>r(\"function\"==typeof t&&t.toString().startsWith(\"class\"),\"a class\",e),toBeFunction:e=>r(\"function\"==typeof t&&!t.toString().startsWith(\"class\"),\"a function\",e),toBeJigClass:e=>r(\"function\"==typeof t&&t.toString().startsWith(\"class\")&&i(t,Jig),\"a jig class\",e),toExtendFrom:(e,n)=>r(i(t,e),\"an extension of \"+(e&&e.name),n)}}",{"deps":{"Jig":{"$dup":["1","deps","Jig"]}}}]}]}
    https://whatsonchain.com/tx/15780ffeefb682cb6cea9c07d9efb295abb60d775d275fb5083a9eb8b948b482