Skip to content

Commit

Permalink
feat: Updated TokenScript documentation WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
micwallace committed Sep 5, 2024
1 parent 262b4a3 commit c36d257
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 3 deletions.
298 changes: 297 additions & 1 deletion pages/framework/card-sdk.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,309 @@
# Card SDK

The Card SDK provides the glue between the Javascript defined in the TokenScript project and the engine.
It is essentially a 2-way message bus that allows the TokenScript developer to make requests to the engine and to receive updated data.
It is essentially a 2-way message bus that allows the TokenScript developer to make requests to the engine and to receive updated data and other events emitted by the engine.
It also allows the user to access various data including context data, environment variables & contract information.

## Token Context Data

As explained in the introduction to TokenScript attributes, Token Context data is made available through the card SDK.
Our starter templates already handles the dataChange event which is used to provide initial and updated token context data to your card.

If you want to register your own listener, you can do so by overwriting the following eventHandler

```typescript
tokenscript.tokens.dataChanged = async (oldTokens: any, updatedTokens: ITokenData, id: string) => {

}
```

For our Svelte & React templates, the `updatedTokens.currentInstance` is automatically written to a global context variable that your UI can react to.

## Environment Variables

To access environment variables within your card, ensure that the variables is added into `ts:meta` as demonstrated [here](./tokenscript-syntax/xml).

You can then access these variables via the `tokenscript.env` object:

```typescript
tokenscript.env.MY_ENV_VARIABLE
```

## Event handlers

The engine include event handler infrastructure that allows the registration of multiple event listeners throughout your application.

Currently, there is only one handler however more will be added in the future:

- TRANSACTION_EVENT

```typescript
// Types
interface TokenScriptEvents {
TRANSACTION_EVENT: ITransactionStatus
}

// Usage
tokenscript.on("TRANSACTION_EVENT", (data: ITransactionStatus) => {
// Handle transaction event
})
```

The TRANSACTION_EVENT can be used to react to transaction status even when the default action button is used to trigger the transaction.

## Bundled libraries

To reduce the size of built TokenScripts, some common libraries are bundled with the Card SDK.
This allows the use of these libraries without explicitly installing them via NPM.

Bundled libraries

- Ethers.js ^v6.13 - available via the `ethers` global variable

## Engine & UX actions

### setProps

One of the most important engine actions is `setProps`. It allows the user to define values that can be used as inputs to attributes & transactions:

```typescript
tokenscript.action.setProps({
myAttribute: "123",
myAttribute2: 456
})
```

When invoking this method, the engine checks these values to see if they are `ts:user-entry` attributes.
If they are not user entry values the engine checks if any of these values are a dependency for another attribute defined in the current scope.
If so the engine will reload those attributes and return the updated data to the card via the `dataChanged` handler explained above.
In this way, attribute values can be updated automatically based on an input that is set programmatically from within the card.

Likewise, declarative transactions can reference these values as transactions arguments.

Both attributes & transactions reference these values using the `local-ref` XML attribute:

```xml
<ts:data>
<ts:address ref="ownerAddress"/>
<ts:address local-ref="toAddress"/>
<ts:uint256 ref="tokenId"/>
</ts:data>
```

### show/hideLoader

A self-explanatory method, this allows the user to show or hide the default card loaded provided the TokenScript viewer UI:

`tokenscript.action.showLoader()` OR `tokenscript.action.hideLoader()`.

### setActionButton

The setActionButton method allows the developer to control the state of the default action button:

```typescript
// Type
type setActionButton = (options: { show?: boolean, disable?: boolean, text?: string }) => void

// Usage
tokenscript.action.setActionButton({
show: true, // Whether to show the button
disable: false, // Whether to make the button disabled
text: "Transfer Now" // The text of the button
})
```

This allows the user to hide, disable or update the button text based on some conditions defined in Javascript.

### executeTransaction

`executeTransaction` is used to invoke declarative transactions programmatically as discussed in [TokenScript Transactions](./tokenscript-syntax/transactions).

```typescript
// Types
interface TXOptions {
txName?: string
}

interface ITransactionStatus {
status: 'started'|'aborted'|'submitted'|'confirmed'|'completed',
txNumber?: string,
txLink?: string,
txRecord?: any
}

interface ITransactionListener {
(data: ITransactionStatus): void|Promise<void>
}

type ExecuteTransaction = (optionsOrTxName?: string|TXOptions, listener?: ITransactionListener) => Promise<ITransactionStatus|false>

// Usage

// 1. Execute the default transaction (specified in `ts:card`)
if (!await tokenscript.action.executeTransaction()){
return; // User aborted transaction
}
// Do some post-transaction processing

// 2. Execute a top-level named transaction, with some input values from Javascript
tokenscript.action.setProps({
secretCode: "1234"
});
if (!await tokenscript.action.executeTransaction("levelUp")){
return; // User aborted transaction
}

// 3. Use a transaction progress listener
await tokenscript.action.executeTransaction("levelUp", (txInfo) => {
// Update UI based on transaction process
});

```

Since the executeTransaction method is async, it can be used to chain together transactions and other off-chain processes.

### showMessageToast

This allows the developer to show message notifications using the toast notification system provided by TokenScript viewer.
Along with the other UI specific methods, this helps keep the user-experience consistent across all TokenScript applications.

```typescript
// Type
type ShowMessageToast = (type: 'success'|'info'|'warning'|'error', title: string, description: string) => void;

// Usage
tokenscript.action.showMessageToast("error", "An error occurred", "Detailed information about the error");
```

### showTransactionToast

Like the previous method, this allows the user to show transaction specific notifications:

```typescript
// Type
type ShowTransactionToast = (status: "submitted"|"confirmed", chain: number, txHash: string) => void;

// Usage
tokenscript.action.showTransactionToast('confirmed', 137, "0x05b9d22ce9ac87e399c18c488c3e02327945ffb4633890030789ea1f2f783e0b");
```

Note: This method only needs to be used when using the injected ethereum provider. If you are using declarative transactions, notifications are already taken care of by the engine.

### closeCard

This method simply closes the current card:

```typescript
tokenscript.action.closeCard();
```

### openCard

You can also switch between cards using the `openCard` method.

```typescript
// Type
type OpenCard = (name: string, originId?: string, tokenId?: string) => void;

// Usage
tokenscript.action.openCard({
name: "myCardName",
originId: "ReallyCoolToken", // This is the name of the origin contract as defined in the tokenscript.xml.
tokenId: "1" // The tokenId we want to open the card for
});
```

If originId or tokenId are not provided, the current token context is used to open the new card.

## Ethereum methods

### Signing

You can request the user to sign a personal message like this:

```typescript
// Type
type SignPersonal = (msgParams: {data: string|Uint8Array}, callback?: (error: any, data: string) => void) => void|Promise<string>;

// Usage
const signature = await tokenscript.personal.sign({data: "My message"});
```

This method is async but can also take a callback as well.

**Note: If you need typed data signing, use the Ethereum Provider**

### getRpcUrls

The `getRpcUrls` method provides an array of RPC URLs for the specified chain ID:

```typescript
// Type
type GetRpcUrls = (chain: number) => string[];

// Usage
const rpcUrls = tokenscript.eth.getRpcUrls(137);
````

These URLs are configured and provided by the TokenScript Engine.

### getRpcProvider

Instead of requesting URLs and configuring the ethereum provider yourself, you can get an ethers.js provider directly:

```typescript
// Type
type GetRpcProvider = (chain: number) => AbstractProvider; // ethers.AbstractProvider
// Usage
const provider = tokenscript.eth.getRpcProvider(137);
```

If more than one RPC URL is defined for the specified chain ethers.FallbackProvider is returned, otherwise ethers.JsonRpcProvider is returned.

### getContractInfo

It's often helpful to get contract information based on the name specified in the TokenScript XML.
In this way there is no need to hard-code contract addresses and ABI JSON within your Javascript code.
Instead, this config information can be specified within the `tokenscript-project.json` environment variables and made available via this API method.

```typescript
// Type
type GetContractInfo = (name: string, chain?: number) => {chain: number, address: string, abi: any};
// Usage
const contractInfo = tokenscript.eth.getContractInfo("MyReallyCoolToken");
```

If the chain parameter isn't provided, the chain of the current token is used or the first chain specified within `ts:contract`.

### getContractInstance

Just like getContractInfo, we can get an ethers.Contract instance based on the name & chain provided.
These contracts must have an ABI specified within `ts:contract` for the methods you want to execute.

```typescript
// Type
type GetContractInstance = (name: string, chain?: number) => Contract; // ethers.Contract
// Usage
const contract = tokenscript.eth.getContractInstance("MyReallyCoolToken"); // ABI with getTokenPoints is defined in `ts:contract`
const points = await contract.getTokenPoints(token.tokenId);
```

This is most helpful when we want to read contract data within a card, without the use of TokenScript attributes that are loaded when the card is open.

## Ethereum provider

To allow scenarios that are not supported by TokenScript's declarative transactions, the Card SDK provides an EIP-1193 proxy.
This is the standard injected provider (`window.ethereum`) that you would be familiar with from integrating with Metamask and other browser extension wallets.

To use this provider, it's recommended that you wrap it with Ethers.js BrowserProvider like so:
```typescript
const provider = new ethers.BrowserProvider(window.ethereum);
// You can then get an ethers contract instance to interact with a contract
const contract = new ethers.Contract(contractAddress, abi, provider);
```

**Note: When using the injected provider, you must take care of chain switching, transaction notifications & error handling yourself.**
9 changes: 7 additions & 2 deletions pages/framework/tokenscript-syntax/transactions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ If the card does not contain a transaction, the `window.onConfirm` function is t
An example of this would be if an API call or Ethereum signature is needed to get some transaction inputs.
In this case we can perform my transaction prep and execute the transaction programmatically:

```javascript
```typescript
window.onConfirn = async () => {
const res = await getApiData(); // Get some off-chain data to use in our transaction
tokenscript.action.setProps({myAttribute: res.myAttribute});
Expand All @@ -122,4 +122,9 @@ In this way, even the default action button can be used to chain transactions to
Sometimes a single action button isn't enough, so transactions can be invoked programmatically using the Card SDK.
See the [Card SDK documentation](../card-sdk) for more details.

When the default action button isn't needed, it can be disabled. See [here](./cards#uiButton) for further details.
When the default action button isn't needed, it can be disabled. See [here](./cards#uiButton) for further details.

## Ethereum provider

To allow scenarios that are not supported by TokenScript's declarative transactions, the Card SDK provides a EIP-1193 proxy.
Learn how to use it [here](../card-sdk#ethereum-provider).

0 comments on commit c36d257

Please sign in to comment.