token script study
https://www.tokenscript.org/
https://community.tokenscript.org/
https://medium.com/alphawallet/tokenscript/home
https://github.com/TokenScript/TokenScript/tree https://github.com/TokenScript/TokenScriptTestContracts/tree/master https://github.com/TokenScript/TokenScript-Examples/tree/master/tutorial
EntryToken.sol
pragma solidity ^0.4.25; //
contract EntryToken
{
mapping(address => uint256[]) inventory;
uint256[] public spawnedTokens;
mapping(bytes32 => bool) signatureChecked;
address public organiser;
address public paymaster;
string public name;
uint8 public constant decimals = 0; //no decimals as Tokens cannot be split
bool expired = false;
string public state;
string public locality;
string public street;
string public building;
string public symbol;
bytes4 balHash = bytes4(keccak256('balanceOf(address)'));
bytes4 tradeHash =
bytes4(keccak256('trade(uint256,uint256[],uint8,bytes32,bytes32)'));
bytes4 passToHash =
bytes4(keccak256('passTo(uint256,uint256[],uint8,bytes32,bytes32,address)'));
bytes4 spawnPassToHash =bytes4(keccak256('spawnPassTo(uint256,uint256[],uint8,bytes32,bytes32,address)'
));
event Transfer(address indexed _to, uint256 count);
event TransferFrom(address indexed _from, address indexed _to, uint256
count);
event Trade(address indexed seller, uint256[] TokenIndices, uint8 v, bytes32
r, bytes32 s);
event PassTo(uint256[] TokenIndices, uint8 v, bytes32 r, bytes32 s, address
indexed recipient);
modifier organiserOnly()
{
require(msg.sender == organiser);
_;
}
modifier payMasterOnly()
{
require(msg.sender == paymaster);
_;
}
function() payable public { revert(); } //should not send any ether directly
constructor (
uint256[] Tokens,
string buildingName,
string streetName,
string localityName,
string stateName,
string symbolName,
string contractName) public
{
organiser = msg.sender;
paymaster = msg.sender;
inventory[msg.sender] = Tokens;
building = buildingName;
street = streetName;
locality = localityName;
state = stateName;
symbol = symbolName;
name = contractName;
}
function supportsInterface(bytes4 interfaceID) external view returns (bool)
{
if(interfaceID == balHash
|| interfaceID == tradeHash
|| interfaceID == passToHash
|| interfaceID == spawnPassToHash) return true;
return false;
}
function isExpired(uint256 tokenId) public view returns(bool)
{
return expired;
}
function getStreet(uint256 tokenId) public view returns(string)
{
return street;
}
function getBuildingName(uint256 tokenId) public view returns(string)
{
return building;
}
function getState(uint256 tokenId) public view returns(string)
{
return state;
}
function getLocality(uint256 tokenId) public view returns(string)
{
return locality;
}
function getDecimals() public pure returns(uint)
{
return decimals;
}
function name() public view returns(string)
{
return name;
}
function setExpired(uint256[] tokenIds, bool isExpired) public organiserOnly
{
expired = isExpired;
}
function setStreet(uint256[] tokenIds, string newStreet) public
organiserOnly returns(string)
{
street = newStreet;
}
function setBuilding(uint256[] tokenIds, string newBuildingName) public
organiserOnly returns(string)
{
building = newBuildingName;
}
function setState(uint256[] tokenIds, string newState) public organiserOnly
returns(string)
{
state = newState;
}
function setLocality(uint256[] tokenIds, string newLocality) public organiserOnly returns(string)
{
locality = newLocality;
}
// example: 0, [3, 4], 27, "0x9CAF1C785074F5948310CD1AA44CE2EFDA0AB19C308307610D7BA2C74604AE98", "0x23D8D97AB44A2389043ECB3C1FB29C40EC702282DB6EE1D2B2204F8954E4B451"
// price is encoded in the server and the msg.value is added to the message digest,
// if the message digest is thus invalid then either the price or something else in the message is invalid
function trade(uint256 expiry,
uint256[] TokenIndices,
uint8 v,
bytes32 r,
bytes32 s) public payable
{
//checks expiry timestamp,
//if fake timestamp is added then message verification will fail
require(expiry > block.timestamp || expiry == 0);
bytes32 message = encodeMessage(msg.value, expiry, TokenIndices);
address seller = ecrecover(message, v, r, s);
require(seller == organiser); //only contract owner can issue magiclinks
for(uint i = 0; i < TokenIndices.length; i++)
{ // transfer each individual Tokens in the ask order
uint256 index = TokenIndices[i];
assert(inventory[seller][index] != uint256(0)); // 0 means Token gone.
inventory[msg.sender].push(inventory[seller][index]);
// 0 means Token gone.
delete inventory[seller][index];
}
seller.transfer(msg.value);
emit Trade(seller, TokenIndices, v, r, s);
}
function loadNewTokens(uint256[] Tokens) public organiserOnly
{
for(uint i = 0; i < Tokens.length; i++)
{
inventory[organiser].push(Tokens[i]);
}
}
//for new Tokens to be created and given over
//this requires a special magic link format with tokenids inside rather than indicies
function spawnPassTo(uint256 expiry,
uint256[] Tokens,
uint8 v,
bytes32 r,
bytes32 s,
address recipient) public payable
{
require(expiry > block.timestamp || expiry == 0);
bytes32 message = encodeMessageSpawnable(msg.value, expiry, Tokens);
address giver = ecrecover(message, v, r, s);
//only the organiser can authorise this
require(giver == organiser);
require(!signatureChecked[s]);
organiser.transfer(msg.value);
for(uint i = 0; i < Tokens.length; i++)
{
inventory[recipient].push(Tokens[i]);
//log each spawned Token used for a record
spawnedTokens.push(Tokens[i]);
}
//prevent link being reused with the same signature
signatureChecked[s] = true;
}
//check if a spawnable Token that created in a magic link is redeemed
function spawned(uint256 Token) public view returns (bool)
{
for(uint i = 0; i < spawnedTokens.length; i++)
{
if(spawnedTokens[i] == Token)
{
return true;
}
}
return false;
}
function passTo(uint256 expiry,
uint256[] TokenIndices,
uint8 v,
bytes32 r,
bytes32 s,
address recipient) public organiserOnly
{
require(expiry > block.timestamp || expiry == 0);
bytes32 message = encodeMessage(0, expiry, TokenIndices);
address giver = ecrecover(message, v, r, s);
for(uint i = 0; i < TokenIndices.length; i++)
{
uint256 index = TokenIndices[i];
//needs to use revert as all changes should be reversed
//if the user doesnt't hold all the Tokens
assert(inventory[giver][index] != uint256(0));
uint256 Token = inventory[giver][index];
inventory[recipient].push(Token);
delete inventory[giver][index];
}
emit PassTo(TokenIndices, v, r, s, recipient);
}
// Pack value, expiry, Tokens into 1 array
function encodeMessage(uint value, uint expiry, uint256[] TokenIndices)
internal view returns (bytes32)
{
bytes memory message = new bytes(84 + TokenIndices.length * 2);
address contractAddress = getThisContractAddress();
for (uint i = 0; i < 32; i++)
{
message[i] = byte(bytes32(value << (8 * i)));
}
for (i = 0; i < 32; i++)
{
message[i + 32] = byte(bytes32(expiry << (8 * i)));
}
for(i = 0; i < 20; i++)
{
message[64 + i] = byte(bytes20(contractAddress) << (8 * i));
}
for (i = 0; i < TokenIndices.length; i++)
{
message[84 + i * 2 ] = byte(TokenIndices[i] >> 8);
message[84 + i * 2 + 1] = byte(TokenIndices[i]);
}
return keccak256(message);
}
// Pack value, expiry, Tokens into 1 array
function encodeMessageSpawnable(uint value, uint expiry, uint256[] Tokens)
internal view returns (bytes32)
{
bytes memory message = new bytes(84 + Tokens.length * 32);
address contractAddress = getThisContractAddress();
for (uint i = 0; i < 32; i++)
{
message[i] = byte(bytes32(value << (8 * i)));
}
for (i = 0; i < 32; i++)
{
message[i + 32] = byte(bytes32(expiry << (8 * i)));
}
for(i = 0; i < 20; i++)
{
message[64 + i] = byte(bytes20(contractAddress) << (8 * i));
}
for (i = 0; i < Tokens.length; i++)
{
for (uint j = 0; j < 32; j++)
{
message[84 + i * 32 + j] = byte(bytes32(Tokens[i] << (8 * j)));
}
}
return keccak256(message);
}
function getSymbol() public view returns(string)
{
return symbol;
}
function balanceOf(address _owner) public view returns (uint256[])
{
return inventory[_owner];
}
function myBalance() public view returns(uint256[])
{
return inventory[msg.sender];
}
function transfer(address _to, uint256[] TokenIndices) organiserOnly public
{
for(uint i = 0; i < TokenIndices.length; i++)
{
uint index = uint(TokenIndices[i]);
require(inventory[msg.sender][index] != uint256(0));
//pushes each element with ordering
inventory[_to].push(inventory[msg.sender][index]);
delete inventory[msg.sender][index];
}
emit Transfer(_to, TokenIndices.length);
}
function transferFrom(address _from, address _to, uint256[] TokenIndices)
organiserOnly public
{
for(uint i = 0; i < TokenIndices.length; i++)
{
uint index = uint(TokenIndices[i]);
require(inventory[_from][index] != uint256(0));
//pushes each element with ordering
inventory[_to].push(inventory[_from][index]);
delete inventory[_from][index];
}
emit TransferFrom(_from, _to, TokenIndices.length);
}
function endContract() public organiserOnly
{
selfdestruct(organiser);
}
function isStormBirdContract() public pure returns (bool)
{
return true;
}
function getThisContractAddress() public view returns(address)
{
return this;
}
}
EntryToken.xml:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ts:token xmlns:ethereum="urn:ethereum:constantinople"
xmlns:ts="http://tokenscript.org/2020/06/tokenscript"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="entrytoken"
xsi:schemaLocation="http://tokenscript.org/2020/06/tokenscript http://tokenscript.org/2020/06/tokenscript.xsd"
>
<ts:label>
<ts:plurals xml:lang="en">
<ts:string quantity="one">Ticket</ts:string>
<ts:string quantity="other">Tickets</ts:string>
</ts:plurals>
<ts:plurals xml:lang="es">
<ts:string quantity="one">Boleto de admisión</ts:string>
<ts:string quantity="other">Boleto de admisiónes</ts:string>
</ts:plurals>
<ts:plurals xml:lang="zh">
<ts:string quantity="one">入場券</ts:string>
<ts:string quantity="other">入場券</ts:string>
</ts:plurals>
</ts:label>
<ts:contract interface="erc875" name="EntryToken">
<ts:address network="1">0x63cCEF733a093E5Bd773b41C96D3eCE361464942</ts:address>
<ts:address network="3">0xFB82A5a2922A249f32222316b9D1F5cbD3838678</ts:address>
<ts:address network="4">0x59a7a9fd49fabd07c0f8566ae4be96fcf20be5e1</ts:address>
<ts:address network="42">0x2B58A9403396463404c2e397DBF37c5EcCAb43e5</ts:address>
</ts:contract>
<ts:origins>
<!-- Define the contract which holds the token that the user will use -->
<ts:ethereum contract="EntryToken"></ts:ethereum>
</ts:origins>
<ts:selection filter="expired=TRUE" name="expired">
<ts:label>
<ts:plurals xml:lang="en">
<ts:string quantity="one">Expired Ticket</ts:string>
<ts:string quantity="other">Expired Tickets</ts:string>
</ts:plurals>
<ts:string xml:lang="zh">已经过期的票</ts:string>
</ts:label>
</ts:selection>
<ts:cards>
<ts:card name="main" type="token">
<ts:item-view xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<style type="text/css">.ts-count {
font-family: "SourceSansPro";
font-weight: bolder;
font-size: 21px;
color: rgb(117, 185, 67);
}
.ts-category {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 21px;
color: rgb(67, 67, 67);
}
.ts-venue {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 16px;
color: rgb(67, 67, 67);
}
.ts-date {
font-family: "SourceSansPro";
font-weight: bold;
font-size: 14px;
color: rgb(112, 112, 112);
margin-left: 7px;
margin-right: 7px;
}
.ts-time {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 16px;
color: rgb(112, 112, 112);
}
html {
}
body {
padding: 0px;
margin: 0px;
}
div {
margin: 0px;
padding: 0px;
}
.data-icon {
height:16px;
vertical-align: middle
}
.tbml-count { font-family: "SourceSansPro"; font-weight: bolder; font-size: 21px; color: rgb(117, 185, 67);}.tbml-category { font-family: "SourceSansPro"; font-weight: lighter; font-size: 21px; color: rgb(67, 67, 67);}.tbml-venue { font-family: "SourceSansPro"; font-weight: lighter; font-size: 16px; color: rgb(67, 67, 67);}.tbml-date { font-family: "SourceSansPro"; font-weight: bold; font-size: 14px; color: rgb(112, 112, 112); margin-left: 7px; margin-right: 7px;}.tbml-time { font-family: "SourceSansPro"; font-weight: lighter; font-size: 16px; color: rgb(112, 112, 112);} html { } body { padding: 0px; margin: 0px; } div { margin: 0px; padding: 0px; } .data-icon { height:16px; vertical-align: middle }
</style>
<body><div>
<!-- Iconified view displayed on the first page when clicking on token card in AlphaWallet -->
<p>Enter Satoshi's villa with this special token!
<img alt="" src="data:image/jpeg;base64,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"></img>
</p>
</div>
</body>
</ts:item-view>
<ts:view xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<style type="text/css">.ts-count {
font-family: "SourceSansPro";
font-weight: bolder;
font-size: 21px;
color: rgb(117, 185, 67);
}
.ts-category {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 21px;
color: rgb(67, 67, 67);
}
.ts-venue {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 16px;
color: rgb(67, 67, 67);
}
.ts-date {
font-family: "SourceSansPro";
font-weight: bold;
font-size: 14px;
color: rgb(112, 112, 112);
margin-left: 7px;
margin-right: 7px;
}
.ts-time {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 16px;
color: rgb(112, 112, 112);
}
html {
}
body {
padding: 0px;
margin: 0px;
}
div {
margin: 0px;
padding: 0px;
}
.data-icon {
height:16px;
vertical-align: middle
}
.tbml-count { font-family: "SourceSansPro"; font-weight: bolder; font-size: 21px; color: rgb(117, 185, 67);}.tbml-category { font-family: "SourceSansPro"; font-weight: lighter; font-size: 21px; color: rgb(67, 67, 67);}.tbml-venue { font-family: "SourceSansPro"; font-weight: lighter; font-size: 16px; color: rgb(67, 67, 67);}.tbml-date { font-family: "SourceSansPro"; font-weight: bold; font-size: 14px; color: rgb(112, 112, 112); margin-left: 7px; margin-right: 7px;}.tbml-time { font-family: "SourceSansPro"; font-weight: lighter; font-size: 16px; color: rgb(112, 112, 112);} html { } body { padding: 0px; margin: 0px; } div { margin: 0px; padding: 0px; } .data-icon { height:16px; vertical-align: middle }
</style>
<script type="text/javascript">//
(function() {
'use strict'
function GeneralizedTime(generalizedTime) {
this.rawData = generalizedTime;
}
GeneralizedTime.prototype.getYear = function () {
return parseInt(this.rawData.substring(0, 4), 10);
};
GeneralizedTime.prototype.getMonth = function () {
return parseInt(this.rawData.substring(4, 6), 10) - 1;
};
GeneralizedTime.prototype.getDay = function () {
return parseInt(this.rawData.substring(6, 8), 10)
};
GeneralizedTime.prototype.getHours = function () {
return parseInt(this.rawData.substring(8, 10), 10)
};
GeneralizedTime.prototype.getMinutes = function () {
var minutes = parseInt(this.rawData.substring(10, 12), 10)
if (minutes) return minutes
return 0
};
GeneralizedTime.prototype.getSeconds = function () {
var seconds = parseInt(this.rawData.substring(12, 14), 10)
if (seconds) return seconds
return 0
};
GeneralizedTime.prototype.getMilliseconds = function () {
var startIdx
if (time.indexOf('.') !== -1) {
startIdx = this.rawData.indexOf('.') + 1
} else if (time.indexOf(',') !== -1) {
startIdx = this.rawData.indexOf(',') + 1
} else {
return 0
}
var stopIdx = time.length - 1
var fraction = '0' + '.' + time.substring(startIdx, stopIdx)
var ms = parseFloat(fraction) * 1000
return ms
};
GeneralizedTime.prototype.getTimeZone = function () {
let time = this.rawData;
var length = time.length
var symbolIdx
if (time.charAt(length - 1 ) === 'Z'){
return 0
}
if (time.indexOf('+') !== -1) {
symbolIdx = time.indexOf('+')
} else if (time.indexOf('-') !== -1) {
symbolIdx = time.indexOf('-')
} else {
return NaN
}
var minutes = time.substring(symbolIdx + 2)
var hours = time.substring(symbolIdx + 1, symbolIdx + 2)
var one = (time.charAt(symbolIdx) === '+') ? 1 : -1
var intHr = one * parseInt(hours, 10) * 60 * 60 * 1000
var intMin = one * parseInt(minutes, 10) * 60 * 1000
var ms = minutes ? intHr + intMin : intHr
return ms
};
if (typeof exports === 'object') {
module.exports = GeneralizedTime
} else if (typeof define === 'function') {
define(GeneralizedTime)
} else {
window.GeneralizedTime = GeneralizedTime
}
}())
class Token {
constructor(tokenInstance) {
this.props = tokenInstance;
}
formatGeneralizedTimeToDate(str) {
const d = new GeneralizedTime(str);
return new Date(d.getYear(), d.getMonth(), d.getDay(), d.getHours(), d.getMinutes(), d.getSeconds()).toLocaleDateString();
}
formatGeneralizedTimeToTime(str) {
const d = new GeneralizedTime(str);
return new Date(d.getYear(), d.getMonth(), d.getDay(), d.getHours(), d.getMinutes(), d.getSeconds()).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
render() {
let time;
let date;
if (this.props.time == null) {
time = "";
date = "";
} else {
time = this.formatGeneralizedTimeToTime(this.props.time.generalizedTime);
date = this.props.time == null ? "": this.formatGeneralizedTimeToDate(this.props.time.generalizedTime);
}
return `<div>
<div>
<span class="ts-count">x${this.props._count}</span> <span class="ts-category">${this.props.label}</span>
</div>
<div>
<span class="ts-venue">${this.props.building}</span>
</div>
<div style="margin: 0px; padding:0px; clear: both; height: 6px">
&nbsp;
</div>
<div>
<img src="data:image/png;base64,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" class="data-icon"/>
<span class="ts-date">${date}</span>
</div>
<div>
<span class="ts-time">${time}, ${this.props.locality}</span>
</div>
</div>`;
}
}
web3.tokens.dataChanged = (oldTokens, updatedTokens, tokenCardId) => {
const currentTokenInstance = updatedTokens.currentInstance;
document.getElementById(tokenCardId).innerHTML = new Token(currentTokenInstance).render();
};
//
</script>
</ts:view>
</ts:card>
<ts:card exclude="expired" name="enter" type="action">
<!-- this action is of the model confirm-back.
It should be <ts:card type="action" model="confirm-back"> but Weiwu
shied away from specifying that due to the likely change of design causing an upgrade path issue.
window.onConfirm is called if user hit "confirm";
window.close() causes the back button to be pressed.
-->
<ts:label>
<ts:string xml:lang="en">Enter</ts:string>
<ts:string xml:lang="zh">入場</ts:string>
<ts:string xml:lang="es">Entrar</ts:string>
</ts:label>
<ts:view xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<style type="text/css">.ts-count {
font-family: "SourceSansPro";
font-weight: bolder;
font-size: 21px;
color: rgb(117, 185, 67);
}
.ts-category {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 21px;
color: rgb(67, 67, 67);
}
.ts-venue {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 16px;
color: rgb(67, 67, 67);
}
.ts-date {
font-family: "SourceSansPro";
font-weight: bold;
font-size: 14px;
color: rgb(112, 112, 112);
margin-left: 7px;
margin-right: 7px;
}
.ts-time {
font-family: "SourceSansPro";
font-weight: lighter;
font-size: 16px;
color: rgb(112, 112, 112);
}
html {
}
body {
padding: 0px;
margin: 0px;
}
div {
margin: 0px;
padding: 0px;
}
.data-icon {
height:16px;
vertical-align: middle
}
.tbml-count { font-family: "SourceSansPro"; font-weight: bolder; font-size: 21px; color: rgb(117, 185, 67);}.tbml-category { font-family: "SourceSansPro"; font-weight: lighter; font-size: 21px; color: rgb(67, 67, 67);}.tbml-venue { font-family: "SourceSansPro"; font-weight: lighter; font-size: 16px; color: rgb(67, 67, 67);}.tbml-date { font-family: "SourceSansPro"; font-weight: bold; font-size: 14px; color: rgb(112, 112, 112); margin-left: 7px; margin-right: 7px;}.tbml-time { font-family: "SourceSansPro"; font-weight: lighter; font-size: 16px; color: rgb(112, 112, 112);} html { } body { padding: 0px; margin: 0px; } div { margin: 0px; padding: 0px; } .data-icon { height:16px; vertical-align: middle }
</style>
<script type="text/javascript">//
class Token {
constructor(tokenInstance) {
this.props = tokenInstance
document.getElementById("contractAddress").value = this.props.EntryToken;
}
}
web3.tokens.dataChanged = (oldTokens, updatedTokens, tokenCardId) => {
const currentTokenInstance = updatedTokens.currentInstance;
document.getElementById(tokenCardId).innerHTML = new Token(currentTokenInstance).render();
};
document.addEventListener("DOMContentLoaded", function() {
window.onload = function startup() {
// 1. call API to fetch challenge
fetch('http://stormbird.duckdns.org:8080/api/getChallenge')
.then(function (response) {
return response.text()
})
.then(function (response) {
document.getElementById('msg').innerHTML = 'Challenge: ' + response
window.challenge = response
})
}
window.onConfirm = function onConfirm(signature) {
if (window.challenge === undefined || window.challenge.length == 0) return
const challenge = window.challenge
document.getElementById('status').innerHTML = 'Wait for signature...'
// 2. sign challenge to generate response
web3.personal.sign({ data: challenge }, function (error, value) {
if (error != null) {
document.getElementById('status').innerHTML = error
window.onload();
return
}
document.getElementById('status').innerHTML = 'Verifying credentials ...'
// 3. open door
let contractAddress = document.getElementById("contractAddress").textContent;
fetch(`http://stormbird.duckdns.org:8080/api/checkSignature?contract=${contractAddress}&challenge=${challenge}&sig=${value}`)
.then(function (response) {
return response.text()
})
.then(function (response) {
if (response == "pass") {
document.getElementById('status').innerHTML = 'Entrance granted!'
window.close()
} else {
document.getElementById('status').innerHTML = 'Failed with: ' + response
}
})
});
window.challenge = '';
document.getElementById('msg').innerHTML = '';
}
});
//
</script>
<body><h3>Welcome to Craig Wright's house!</h3>
<div id="msg">Preparing to unlock the entrance door.</div>
<div id="contractAddress"></div>
<div id="status"></div>
</body>
</ts:view>
</ts:card>
</ts:cards>
<ts:attribute name="tokenId" distinct="true">
<ts:type>
<ts:syntax>1.3.6.1.4.1.1466.115.121.1.40</ts:syntax>
</ts:type>
<ts:origins>
<ethereum:call function="balanceOf" contract="EntryToken">
<ts:data>
<ts:uint256 ref="ownerAddress"></ts:uint256>
</ts:data>
</ethereum:call>
</ts:origins>
</ts:attribute>
<ts:attribute name="locality">
<ts:type><ts:syntax>1.3.6.1.4.1.1466.115.121.1.15</ts:syntax></ts:type>
<ts:origins>
<ethereum:call as="utf8" contract="EntryToken" function="getLocality">
<ts:data>
<ts:uint256 ref="tokenId"></ts:uint256>
</ts:data>
</ethereum:call>
</ts:origins>
</ts:attribute>
<ts:attribute name="time">
<ts:type><ts:syntax>1.3.6.1.4.1.1466.115.121.1.24</ts:syntax></ts:type>
<ts:label>
<ts:string xml:lang="en">Time</ts:string>
<ts:string xml:lang="zh">时间</ts:string>
</ts:label>
<ts:origins>
<ts:token-id as="utf8" bitmask="
0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000
474d542b33000000000000000000000001075b2282f05255534b534101050002
"></ts:token-id>
</ts:origins>
</ts:attribute>
<ts:attribute name="expired"> <!-- boolean -->
<ts:type><ts:syntax>1.3.6.1.4.1.1466.115.121.1.7</ts:syntax></ts:type>
<ts:origins>
<ethereum:call as="bool" contract="EntryToken" function="isExpired">
<ts:data>
<ts:uint256 ref="tokenId"></ts:uint256>
</ts:data>
</ethereum:call>
</ts:origins>
</ts:attribute>
<ts:attribute name="street"> <!-- string -->
<ts:type><ts:syntax>1.3.6.1.4.1.1466.115.121.1.15</ts:syntax></ts:type>
<ts:origins>
<ethereum:call as="utf8" contract="EntryToken" function="getStreet">
<ts:data>
<ts:uint256 ref="tokenId"></ts:uint256>
</ts:data>
</ethereum:call>
</ts:origins>
</ts:attribute>
<ts:attribute name="building"> <!-- string -->
<ts:type><ts:syntax>1.3.6.1.4.1.1466.115.121.1.15</ts:syntax></ts:type>
<ts:origins>
<ethereum:call as="utf8" contract="EntryToken" function="getBuildingName">
<ts:data>
<ts:uint256 ref="tokenId"></ts:uint256>
</ts:data>
</ethereum:call>
</ts:origins>
</ts:attribute>
<ts:attribute name="state"> <!-- string -->
<ts:type><ts:syntax>1.3.6.1.4.1.1466.115.121.1.15</ts:syntax></ts:type>
<ts:origins>
<ethereum:call as="utf8" contract="EntryToken" function="getState">
<ts:data>
<ts:uint256 ref="tokenId"></ts:uint256>
</ts:data>
</ethereum:call>
</ts:origins>
</ts:attribute>
</ts:token>
OnConfirm:
@objc func proceed() {
let javaScriptToCallConfirm = """
if (window.onConfirm != null) {
onConfirm()
}
"""
tokenScriptRendererView.inject(javaScript: javaScriptToCallConfirm)
let userEntryIds = action.attributes.values.compactMap { $0.userEntryId }
let fetchUserEntries = userEntryIds
.map { "document.getElementById(\"\($0)\").value" }
.compactMap { tokenScriptRendererView.inject(javaScript: $0) }
guard let navigationController = navigationController else { return }
TokenScript.performTokenScriptAction(action, token: token, tokenId: tokenId, tokenHolder: tokenHolder, userEntryIds: userEntryIds, fetchUserEntries: fetchUserEntries, localRefsSource: tokenScriptRendererView, assetDefinitionStore: assetDefinitionStore, keystore: keystore, server: server, session: session, confirmTokenScriptActionTransactionDelegate: self, navigationController: navigationController)
}
https://github.com/AlphaWallet/alpha-wallet-ios/blob/07c8ad7f6770df647525504b7d955e0f85a56b55/AlphaWallet/Tokens/ViewControllers/TokenInstanceActionViewController.swift#L154
https://github.com/TokenScript/EIP5169TokenFactory/