3. Fetch and display shareholders - PolymathNetwork/whitelist-standalone GitHub Wiki
One of the most used modules in Polymath protocol is the GeneralTransferManager
module or GTM
. It's a free module that gets attached to all newly created tokens by default. It allows token owner to define a white list of token holders (called shareholders by the SDK). Here's the model of a token holder recorded on the GTM
:
- address: Ethereum address of this token holder.
- canTransferAfter (boolean): The date after which they can transfer their holdings.
- canReceiveAfter (Date): The date after which they can receive tokens.
- kycExpiry (Date): The date when this record expires.
- canBuyFromSto (boolean): Whether they can buy from STO directly (versus receiving tokens through minting).
- isAccredited (boolean): Whether they're accredited investors or not.
Checkout this document for more information about the GTM: General-Transfer-Manager.
Like previous sections, we're going to use the SDK to fetch token shareholders, then we'll display them in a simple Ant Design Table
component.
// Whitelist.js
function formatDate(input) {
// Format a date using momentjs.
return moment(input).format('YYYY-MM-DD')
}
// Return boolean values as an Icon.
function formatBool(input) {
return input ?
<Fragment><Icon type="check-circle" theme="filled"/><span>Yes</span></Fragment> :
<Fragment><Icon type="close-circle" theme="filled"/><span>No</span></Fragment>
}
export WhitelistTable = ({tokenholders}) => {
return (
<Table dataSource={tokenholders} rowKey="address">
<Column
title='Address'
dataIndex='address'
render={(text) => <Text>{text}</Text>}
/>
<Column
title='Can send after'
dataIndex='canSendAfter'
render={(text) => formatDate(text)}
/>
<Column
title='Can receive after'
dataIndex='canReceiveAfter'
render={(text) => formatDate(text)}
/>
<Column
title='KYC expiry'
dataIndex='kycExpiry'
render={(text) => formatDate(text)}
/>
<Column
title='Can buy from STO'
dataIndex='canBuyFromSto'
render={(text) => formatBool(text)}
/>
<Column
title='Is accredited'
dataIndex='isAccredited'
render={(text) => formatBool(text)}
/>
<Column render={(text, record) => {
return (
<Fragment>
// We'll create openForm() in a later section
<Button onClick={() => openForm(record.address)}>
<Icon type="edit" theme="filled" />
</Button>
// We'll create removeShareholders() in a later section
<Button onClick={() => removeShareholders([record.address])}>
<Icon type="delete" theme="filled" />
</Button>
</Fragment>
)
}}/>
</Table>
)
}
The table above receives shareholders
array as a property. Each object is structures as we layed it out earlier. We format some values before rendering, eg boolean values will be rendered as icons, and Date
type values will be formatted. Additionally, we've added two buttons that we'll use later to edit or delete a record.
Now that we have this component declared in a separate file (Whitelist.js
), we're going to import it into App.,js
, and then append it to the component as shown below.
// App.js
function App() {
const [state, dispatch] = useReducer(reducer,initialState)
const { tokenholders } = state;
return (
<div className="App">
...
...
{ selectedToken !== undefined &&
<WhitelistTable tokenholders={tokenholders} />
}
</div>
)
}
We haven't fetched shareholders array yet, so let's use the SDK for that purpose. As always, we run our async code in a useEffect()
hook. Note that the effect won't run unless user has selected a token, or if we're reloading shareholders list deliberately (via relaodShareholders
). We're going to use the later flag to signal that we need shareholders list refreshed, say after editing or deleting a shareholder.
// App.js
function App() {
...
useEffect(() => {
// Async action to fetch shareholders.
async function fetchShareholders(dispatch, st) {
let shareholders = await st.shareholders.getShareholders()
dispatch({ type: a.SHAREHOLDERS_FETCHED, shareholders })
}
if ( reloadShareholders === true | selectedToken !== undefined ) {
fetchShareholders(dispatch, tokens[selectedToken])
}
}, [tokens, selectedToken, reloadShareholders])
}
Note that contrary to fetching tokens, fetching shareholders is a method of SecurityToken
object rather than a top-level method:
st.shareholders.getShareholders()
vs
polyClient.getSecurityTokens({ walletAddress })
You might have notices that it's under .shareholders
namespace too, that's where all shareholder-related method are grouped.
Fortunately, app state contains the tokens
array we had fetched earlier. So we access the currently selected tokens by tokens[selectedToken]
.
Finally, we'll add a few more cases to the reducer()
function we created earlier. We need to handle two actions:
-
SHAREHOLDERS_FETCHED
, which is dispatched oncegetShareholders
has resolved. In this case we'll stop the spinner and add the shareholders array to state. -
RELOAD_SHAREHOLDERS
, this action will setreloadShareholders
flag totrue
. As we saw in the effect function above, that flag will force reloading shareholders.
// App.js
function reducer() {
...
case a.SHAREHOLDERS_FETCHED:
let { shareholders } = action
return {
...state,
shareholders,
fetching: false,
tip: '',
reloadShareholders: false,
}
case a.RELOAD_SHAREHOLDERS:
return {
...state,
fetching: true,
tip: 'Reloading tokenholders...',
reloadShareholders: true,
}
}
}
That's it. In the next section we will know how to add/edit/delete shareholders to token's whitelist.