Attribute Origins

TokenScript has a declarative part (data-object schema, attribute typing) and a runtime part (views and actions). We usually process the declarative part before running the executable part. However, it is sometimes required to perform on attribute inputs or outputs.

A simple example, say we need to use the token balance as an input to an attribute that is a contract call result; but before we use it - for some reason - we need to double the value. The attribute result is required to be in the rendered view.

Consider this attribute which is a function:

      <ts:attribute-type id="valueWeNeed" syntax="1.3.6.1.4.1.1466.115.121.1.15">
        <ts:origins>
          <ethereum:call function="getValue" as="utf8" contract="MyCoin">
            <ts:data>
              <ts:uint256 ref="inputVal"></ts:bytes32>
            </ts:data>
          </ethereum:call>
        </ts:origins>
      </ts:attribute-type>

It's an attribute that needs to be displayed in the token view. However one of the inputs to the transaction that this function requires some pre-calculation work before submitting to the transaction.

val inputVal = this.props.balance * 2;
document.getElementById("iVal").value = inputVal;

Now this can be picked up by another attribute:

      <ts:attribute-type id="inputVal" syntax="1.3.6.1.4.1.1466.115.121.1.36">
        <ts:label>
          <ts:string>iVal</ts:string> <!-- This is the element name in the JavaScript -->
        </ts:label>
        <ts:origins>
          <ts:element-value as="uint256"></ts:element-value>
        </ts:origins>
      </ts:attribute-type>

the <element-value> is an indication to the parser that this value is obtained from a DOM element value iVal from the view. Therefore the parser knows it has to render the view before it can obtain iVal and then use that in the inputValue attribute for valueWeNeed, like this:

document.getElementById("info").innerText = `Value you needed is ${this.props.valueWeNeed}`;
1 Like

Thanks! So in your scenario, the value flows:

  1. fetched as attribute balance;
  2. doubled in the view, and assigned to element iVal, which becomes attribute inputVal;
  3. used as the input for fetching the attribute valueWeNeed.
  4. (not written but assumed) the value is used in the action's transaction payload.

The attribute balance's value is refreshed by the rules defined elsewhere in the TokenScript, e.g. when the user enters the view, or, when a relevant blockchain event is logged when the view is already open. If the latter happened, somehow the code segment is triggered again when the event occurs:

val inputVal = this.props.balance * 2;
document.getElementById("iVal").value = inputVal;

However, how to trigger that segment of JavaScript after the view is loaded is left to the API design, and we are here focusing on the TokenScript file structure design first.

I hope so far I understand it correctly so far!


Your post made me rethink whether we need user-entry data origin at the outset.

Let's look at why we declare attribute-type and origins.

  • Primarily, because we wish to prepare token layer data to the view, without the view taking the trouble of connecting a node, fetching value or reading users' attestations, or combing

  • Secondly, the use of TokenScript on a server so that the server can extract token data with their own implementation without having to have a JavaScript runtime environment (e.g. from SpringBoot)

So it seems that our use of user-entry to

  • protect user entry from generating data incompatible with smart-contract (through the use of syntax)
  • pre-fill a user-entry value from a token attribute value

are merely side products that add to the TokenScript vocabulary without saving developers much coding or granting much protection. A server can't use such data anyway, and the client already has the view running, so it can just get the data.

If we considering data available in a view is available to the transaction, we can do this directly:

<ts:attribute-type id="valueWeNeed" syntax="1.3.6.1.4.1.1466.115.121.1.15">
    <ts:origins>
      <ethereum:call function="getValue" as="utf8" contract="MyCoin">
        <ts:data>
          <ts:uint256 form-element="iVal" as="int"></ts:bytes32>
        </ts:data>
      </ethereum:call>
    </ts:origins>
</ts:attribute-type>

And the JavaScript code mostly remains:

val inputVal = this.props.balance * 2;
form.iVal.value = inputVal;

Therefore doing away with the declaration of iVal in TokenScript altogether. form-element is a reference to an element in the form in the view, like this

<inptu type="hidden" name="inputVal"…/>

This also means we are getting rid of user-entry, since there shouldn't be any difference

  • if you use a form value in the payload for a call to a smart contract to get a value,
  • if you use a form value in the payload for a transaction to a smart contract.

Now let me explain why I am thinking of using form element instead of document element.

Ideally, TokenScript action, when implemented on the web, should be in its own web-view, or an enclave of some sort, least the website that uses the token inspects what's in the token (e.g. learns the user's balance as the user attempts to checkout).

But we can't guarantee all of the token code will be executed in such a way. We don't know if we can ship that level of security in 2020, and, in some cases, an action is not restricted to be used by the token owner (e.g. "bid-up the price") or the token's primary view is being rendered on a market website, hence there isn't a need for the additional security.

Having each action wrapped in a form has the advantage that the elements are local. You can have have 100 forms in one web page each has an element named balance, otherwise one getElementById("balance") spoils the whole pot.

I would argue you still need the user-input. There is a subtle difference which is the parser instruction. Declaring as element-value (or in your case form-element) is an instruction to the parser that it needs to resolve the value to render the view, whereas user-input value is sourced when the user performs the script action.

Counter to this: if you do use form-element only and always attempt the second pass it may simplify the syntax but then we're effectively back to where things are now, you've just renamed the exising user-input to form-element and still have the ambiguity.

Your abbreviated form is good however; being able to write

<ts:uint256 form-element="iVal" as="int"></ts:uint256>

directly saves having to write an attribute.

I propose something slightly different how about this:

<ts:uint256 form-value="iVal" as="int"></ts:uint256>
<ts:uint256 user-input="myValue" as="int"></ts:uint256>

form-value would be sourced directly from a JavaScript variable and user-input is expected to be a DOM element like this:

val iVal = this.props.balance * 3;    // get <form-value> from JavaScript  require 2 pass
...
<input type="text" id="myValue">  <!-- get myValue from HTML input field -->

We need to agree on a delta for schema 2019/10 -> schema 2020/03

  1. add the new attribute <element-value> as described.
  2. if parser encounters any element-value in the attributes:
    • Build view
    • gather element-value values
    • rebuild token props with the element-value attributes resolved and build view again.

Would you agree this is the delta for schema 2020/03 ?

You proposed that the two form elements iVal and myValue

 <input type="hidden" name="iVal"/>
 <input type="text" name="myValue"/>

to be referred differently. The former with form-value and the latter with user-input. Because a user-agent would need to observe the initialisation of iVal to do the errand of retrieving an attribute-value that depends on it, while the same doesn't need to be done for user-input, since typically only the final value of user's input is referred to from a transaction occurring as the result of the action.

I wish to address it by showing an example scenario of what I wrote earlier:

This also means we are getting rid of user-entry, since there shouldn't be any difference

  • if you use a form value in the payload for a call to a smart contract to get a value,
  • if you use a form value in the payload for a transaction to a smart contract.

For example, if the action is "purchase" and the orderbook is in a smart contract. The user would type what amount to order and get the estimated price.

<input type="text" name="amount"/>

In an orderbook, the more you want to buy, the worse the price, but there is no way of knowing the price unless you check the smart contract. Therefore, there is an attribute for the price depending on the amount:

<attribute-type id="price"…>
   <origin>
     <ethereum:call function="getPrice">
        <data><uint user-input="amount"/></data>
     </ethereum:call>
   </origin>
</attribute-type>

Let's suppose (apparently) the price is displayed in the view. You are right that only the final value of the user's input is used in the resulting transaction, however, the user agent would do a round of errand every time the value for amount is initialised or changed anyway, and we want to do this through the user-agent, not by bothering developers to write the view calls to smart contracts. Furthermore, this made sure if price is used in the resulting transaction, it is consistent with what smart contract's getPrice() return value, rescuing the user from potential JavaScript meltdown (which might result in 0 in the price.

Hence the proposal not distinguishing between a form-value and a user-input but use form-value across the board.

To make this into a two step process this is the way forward:

  1. Implement form-value= tag which means anything sourced from a view DOM object. This replaces the old <user-input> element, and encompasses the method to source values from the rendered view.
  2. These values should be available when the view renders, so in the first example, inputVal is a form-value.
  3. In the first implementation there is no dependency feedback; ultimately - if a value with a form-value attribute associated with it changes then the dependent attributes should be updated and the view re-rendered with the new attribute values.

The above example becomes:

     <ts:attribute-type id="valueWeNeed" syntax="1.3.6.1.4.1.1466.115.121.1.15">
       <ts:origins>
         <ethereum:call function="getValue" as="utf8" contract="MyCoin">
           <ts:data>
             <ts:uint256 form-value="iVal"></ts:uint256>
           </ts:data>
         </ethereum:call>
       </ts:origins>
     </ts:attribute-type>

...

val calcInputVal = this.props.balance * 2;
this.form.iVal.value = calcInputVal ;

If you were to set a variable which depends on the resolution of another attribute type like ensName, would you expect to have to write a form element to resolve this new variable?

e.g. I have defined an element attribute for namehash in the tokenscript, to set it I write this.props.namehash = ens.namehash(this.props.ensName) in the constructor

would you expect such a writer to create a form element in the html, set a listener and then set the value via document.getElementById or would you expect them to simply only have to write this.props.namehash = xxx

I would think that they would expect the former rather than the later as creating a hidden form which doesn't expect any user input to be counterintuitive

Not sure if i am making sense but I would expect that you would only define it via JS rather than creating HTML and setting it

The changes to the XML language is mostly the same for what James Brown and Weiwu suggested. And I don't have any suggestion on that.

Primarily this (or some variation of it):

<ts:uint256 form-value="iVal"></ts:uint256>

But I would like to suggest the JavaScript API which does affect how the XML declarative side of things work. I see there are 2 issues that should be addressed for the entire design:

A. The TokenScript client has to somehow listen/watch for changes to the value iVal provided by the view

B. TokenScript authors have to define a <form> as well as hidden <input> to make this work. I agree with @James-Sangalli. This should be avoided

So again, given this (or some variation of this):

<ts:uint256 form-value="iVal"></ts:uint256>

We provide a new function web3.tokens.setValues() for the TokenScript author. So they do:

val inputVal = this.props.balance * 2
web3.tokens.setValues({
  iVal: inputVal, //hb edit: corrected an error earlier
  //other "form" values that has changed
})

or simply (the previous version only to make it easier to follow from the other examples):

web3.tokens.setValues({
  iVal: this.props.balance * 2,
  //other "form" values that has changed
})

web3.tokens.setValues() needs only be passed the TokenScript form values [1] that has changed (like how setState() works in React). The TokenScript client needs to track the state of these TokenScript form values. These TokenScript form values are declared and referred to in the TokenScript XML with this:

<ts:uint256 form-value="iVal"></ts:uint256>

This addresses both:

(A) No longer necessary to watch for value changes. We don't have to figure out depedencies and the flow is explicit and unidirectional (B) We don't need <form> and hidden <input> anymore

Checking this design against two use-cases aforementioned, U1 and U2.

U1: Sangalli wrote

U2: Weiwu wrote

U1 is already handled, and U2 is solved with this, so we don't need user-entry anymore:

If the user has an HTML form:

web3.tokens.setValues({
  iVal: someForm.amount.value, //iVal aka amount
  //other props that has changed
})

Without an HTML form:

web3.tokens.setValues({
  iVal: getElementById(containerId).getElementsByName["amount"].value, //iVal aka amount
  //other props that has changed
})

[1] "TokenScript form values" refers to the "form" in <ts:uint256 form-value="iVal"></ts:uint256>. It doesn't rely on an HTML form tag

1 Like