# Tokenholder Specifications

{% embed url="<https://github.com/PolymathNetwork/whitelist-standalone>" %}

## :eyes: Overview

This tutorial application will teach you how to add transfer restrictions to token holders in 3 simple steps:

1. Fetch security tokens that belong to the current user.
2. Fetch Tokenholders.
3. Add, update and remove Tokenholders.

## :point\_down: Fetching Tokens

Once we've established an SDK connection, we can fetch the security tokens owned by the current address in Metamask.

```typescript
// App.js
function App() {
  ...
  // b. Fetch tokens
  useEffect(() => {
    // An async action to retrieve relevant tokens.
    async function fetchTokens(dispatch, polyClient, walletAddress) {
      // An auxiliary action to start a spinner.
      dispatch({type: a.FETCHING_TOKENS})

      // Use the SDK to fetch tokens.
      const tokens = await polyClient.getSecurityTokens({ walletAddress })

      const tokenSelectOpts = tokens.map((token, i) =>
        <Option value={i} key={i}>{token.symbol}</Option>)
      // Send fetched tokens to be saved in app state. Also stop the spinner.
      dispatch({type: a.FETCHED_TOKENS, tokens, tokenSelectOpts})
    }

    if (polyClient && walletAddress && !tokens) {
      fetchTokens(dispatch, polyClient, walletAddress)
    }
  }, [walletAddress, polyClient, tokens])
  ...

  return (
    ...
}
```

The SDK will query the `SecurityTokenRegistry` for tokens owned by `walletAddress`.

```typescript
const tokens = await polyClient.getSecurityTokens({ walletAddress })
```

## :handshake: Fetching Tokenholders

Use the SDK to fetch an array of tokenholders. Start by running your async code in a `useEffect()` hook. Note that the effect won't run unless the user has selected a token, or if you're reloading the tokenholders list deliberately (via `relaodTokenholders`).

```typescript
// App.js
function App() {
  ...
  useEffect(() => {
    // Async action to fetch tokenholders.
    async function fetchTokenholders(dispatch, st) {
      let tokenholders = await st.tokenholders.getTokenholders()
      dispatch({ type: a.TOKENHOLDERS_FETCHED, tokenholders })
    }
    if ( reloadTokenholders === true | selectedToken !== undefined ) {
      fetchTokenholders(dispatch, tokens[selectedToken])
    }
  }, [tokens, selectedToken, reloadTokenholders])
}
```

Note that contrary to fetching tokens, fetching tokenholders is a method of the `SecurityToken` entity objet rather than a top-level method:

```typescript
st.tokenholders.getTokenholders()
```

vs

```typescript
polyClient.getSecurityTokens({ walletAddress })
```

Tokenholder-related methods are grouped under the `.tokenholders` namespace.

The app state contains the `tokens` array fetched earlier. You can access the currently selected tokens by `tokens[selectedToken]`.

## :card\_box: Adding, Editing, and Deleting Tokenholders

First, add event handlers to the `App` component. You can add these functions anywhere in the App function:

```typescript
// App.js
function App() {
  ...
  // Used for both adding a new tokenholder and modifying an existing one.
  async function modifyWhitelist(data) {
    const queue = await tokens[selectedToken].tokenholders.modifyData({
      tokenholderData: data
    })
    await queue.run()
    dispatch({type:a.RELOAD_TOKENHOLDERS})
  }

  async function removeTokenholders(addresses) {
    dispatch({type: a.DELETING_TOKENHOLDER})
    const queue = await tokens[selectedToken].tokenholders.revokeKyc({
      tokenholderAddresses: addresses
    })
    await queue.run()
    dispatch({type: a.TOKENHOLDER_DELETED})
    dispatch({type:a.RELOAD_TOKENHOLDERS})
  }
  ...
  return (
    ...
    { selectedToken !== undefined &&
      <Whitelist
        tokenholders={tokenholders}
        // Note these additional properties
        modifyWhitelist={modifyWhitelist}
        removeTokenholders={removeTokenholders}
        />
    }
    ...
  )
```

* `modifyWhitelist`: A submit handler for the tokenholder form. The handler receives an array of  tokenholder data objects (which include the tokenholder Ethereum addresses, buy and sell lockup dates and their KYC expiry dates). Eventually, the function uses the SDK's `modifyData` as follows:

```typescript
const queue = await tokens[selectedToken].tokenholders.modifyData({
  // data is an array of tokenholder objects.
  tokenholderData: data
})
await queue.run()
```

* `removeTokenholders`: A handler for the `delete` button. It will call the SDK's `revokeKyc` to revoke KYC for passed addresses.

Now, add the add/edit tokenholder form. First, create a wrapper component `Whitelist`, which will wrap the form as well as the tokenholders table we've created in previous steps.

```typescript
// Whitelist.js
export default ({toknholders, modifyWhitelist, removeTokenholders}) => {
  const form = useForm()
  // These functions are provided by rc-form-hooks library. See https://github.com/mushan0x0/rc-form-hooks
  const { getFieldDecorator, setFieldsValue, resetFields, validateFields } = form
  // Note this is a different reducer function from the one we created earlier. We're using this reducer particularly for form state.
  const [state, dispatch] = useReducer(reducer, initialState)
  const { visible, editIndex, ongoingTx } = state

  const closeForm = () => {
    dispatch({type: 'CLOSE_FORM'})
    resetFields()
  }
  
  // Passing an index means that we're editing an existing item. Otherwise it's a create form.
  const openForm = (index = '') => {
    dispatch({ type: 'OPEN_FORM', payload: { editIndex: index } })
  }

  const submitForm = async () => {
    const fields = ['address', 'canSendAfter', 'canReceiveAfter', 'kycExpiry', 'canBuyFromSto', 'isAccredited']
    validateFields(fields, { force: true })
      .then(async (values) => {
        // The values below are instances of momentjs. Convert them to JS Date objects as the SDK expects.
        values.canSendAfter = values.canSendAfter.toDate()
        values.canReceiveAfter = values.canReceiveAfter.toDate()
        values.kycExpiry = values.kycExpiry.toDate()

        try {
          dispatch({type: 'TX_SEND'})
          // Call the helper function, which will the SDK in its turn.
          await modifyWhitelist([values])
          dispatch({ type: 'TX_RECEIPT'})
          resetFields()
        }
        catch (error) {
          // This could be transaction error, or a user error e.g user rejected transaction.
          dispatch({ type: 'TX_ERROR',
            payload: {error: error.message} })
          message.error(error.message)
        }
      })
  }

  let editedRecord = tokenholders.filter(tokenholder => tokenholder.address === editIndex)[0]
  // This effect sets form initial values. In "edit" mode, initial values reflect the currently edited record.
  useEffect(() => {
    let initialValues = editedRecord || defaultTokenholderValues
    setFieldsValue(initialValues)
  }, [editedRecord, setFieldsValue])

  return (
    <div style={{display: 'flex',
      flexDirection: 'column'}}>
      <Button onClick={openForm}>Add new</Button>
      {/* This is the component from the previous guide */}
      <TokenholdersTable tokenholders={tokenholders} removeTokenholders={removeTokenholders} openForm={openForm} />
      <Modal
        title={editedRecord ? 'Edit token holder' : 'Add a new token holder'}
        closable={false}
        visible={visible}
        footer={null}
      >
        <Spin spinning={ongoingTx} size="large">
          <Form {...formItemLayout}>
            <Item name="address" label="Address">
              // here we add a couple of address validators
              // - The first make sure that the address is a valid ethereum address
              // - The second make sure we're not adding an existing tokenholder.
              {getFieldDecorator('address', {
                rules: [
                  { required: true  }, {
                    validator: (rule, value, callback) => {
                      if (!editedRecord && !web3Utils.isAddress(value)) {
                        callback('Address is invalid') return
                      }
                      callback() return
                    }
                  }, {
                    validator: (rule, value, callback) => {
                      const tokenholderExists = (address) => {
                        const ret =  tokenholders.find((element) => element.address.toUpperCase() === address.toUpperCase())
                            !== undefined
                        return ret
                      }
                      if (!editedRecord && tokenholderExists(value)) {
                        callback('Tokenholder is already present in the whitelist') return
                      }
                      callback() return
                    }
                  }
                ],
                // Disable address field in case of editing.
              })(<Input disabled={!!editedRecord}/>)}
            </Item>
            <Item name="canSendAfter"  label="Can Send after">
              {getFieldDecorator('canSendAfter', {
                rules: [{ required: true }],
              })(<DatePicker />)}
            </Item>
            <Item name="canReceiveAfter" label="Can Receive After">
              {getFieldDecorator('canReceiveAfter', {
                rules: [{ required: true }],
              })(<DatePicker />)}
            </Item>
            <Item name="kycExpiry" label="KYC Expiry">
              {getFieldDecorator('kycExpiry', {
                rules: [{ required: true }],
              })(<DatePicker />)}
            </Item>
            <Item name="canBuyFromSto" label="Can Buy from STO">
              {getFieldDecorator('canBuyFromSto', {
                valuePropName: 'checked',
              })(<Switch />)}
            </Item>
            <Item name="isAccredited" label="Accredited">
              {getFieldDecorator('isAccredited', {
                valuePropName: 'checked',
              })(<Switch />)}
            </Item>
            <Item>
              <Button onClick={closeForm}>cancel</Button>
              <Button type="primary" onClick={submitForm}>save</Button>
            </Item>
          </Form>
        </Spin>
      </Modal>
    </div>
  )
}
```

Feel free to substitute the Ant Design library used here with any other library you're familiar with.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.polymath.network/developers/tutorials-1/compliance.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
