Skip to content

Commit

Permalink
feat: non ERC721 / ERC1155 tokens functionality SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
daveroga committed Nov 30, 2021
1 parent 6a43b44 commit 95c1ca9
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 5 deletions.
4 changes: 3 additions & 1 deletion cli/lib/abi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TOKEN_TYPE } from './constants.mjs';
import ERC20ABI from './abis/ERC20.mjs';
import ERC721ABI from './abis/ERC721.mjs';
import ERC1155ABI from './abis/ERC1155.mjs';
import ERC165 from './abis/ERC165.mjs';

function getAbi(tokenType) {
switch (tokenType) {
Expand All @@ -11,7 +12,8 @@ function getAbi(tokenType) {
return ERC721ABI;
case TOKEN_TYPE.ERC1155:
return ERC1155ABI;

case TOKEN_TYPE.ERC165:
return ERC165;
default:
return null;
}
Expand Down
21 changes: 21 additions & 0 deletions cli/lib/abis/ERC165.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default [
{
inputs: [
{
internalType: 'bytes4',
name: 'interfaceId',
type: 'bytes4',
},
],
name: 'supportsInterface',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
];
150 changes: 147 additions & 3 deletions cli/lib/tokens.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,65 @@ async function getERCInfo(ercAddress, ethereumAddress, provider, options) {
tokenId = options.tokenId;
}

let tokenType;
let balance;
let decimals = 0;

try {
// Check supportsInterface ERC165 that implements ERC721 and ERC1155
const abi = getAbi(TOKEN_TYPE.ERC165);
const ercContract = new provider.eth.Contract(abi, ercAddress);
const interface721 = await ercContract.methods.supportsInterface('0x80ac58cd').call(); // ERC721 interface
if (interface721) {
tokenType = TOKEN_TYPE.ERC721;
} else {
const interface1155 = await ercContract.methods.supportsInterface('0xd9b67a26').call(); // ERC1155 interface
if (interface1155) tokenType = TOKEN_TYPE.ERC1155;
}
} catch {
// Expected ERC20
tokenType = TOKEN_TYPE.ERC20;
}

if (tokenType === TOKEN_TYPE.ERC721) {
// ERC721
const abi = getAbi(TOKEN_TYPE.ERC721);
const ercContract = new provider.eth.Contract(abi, ercAddress);
balance = await ercContract.methods.balanceOf(ethereumAddress).call();
} else if (tokenType === TOKEN_TYPE.ERC1155) {
// ERC1155
const abi = getAbi(TOKEN_TYPE.ERC1155);
const ercContract = new provider.eth.Contract(abi, ercAddress);
balance = await ercContract.methods.balanceOf(ethereumAddress, tokenId).call();
} else {
// expected ERC20
try {
const abi = getAbi(TOKEN_TYPE.ERC20);
const ercContract = new provider.eth.Contract(abi, ercAddress);
balance = await ercContract.methods.balanceOf(ethereumAddress).call();
if (toEth) {
decimals = await getDecimals(ercAddress, TOKEN_TYPE.ERC20, provider);
balance = fromBaseUnit(balance, decimals);
}
} catch {
throw new Error('Unknown token type', ercAddress);
}
}

return { balance, decimals, tokenType };

/*
try {
const abi = getAbi(TOKEN_TYPE.ERC20);
const ercContract = new provider.eth.Contract(abi, ercAddress);
let balance = await ercContract.methods.balanceOf(ethereumAddress).call();
if (toEth) {
const decimals = await getDecimals(ercAddress, TOKEN_TYPE.ERC20, provider);
balance = fromBaseUnit(balance, decimals);
return { balance, decimals, tokenType: TOKEN_TYPE.ERC20 };
tokenType = TOKEN_TYPE.ERC20;
return { balance, decimals, tokenType };
}
return { balance, tokenType: TOKEN_TYPE.ERC20 };
return { balance, tokenType };
} catch {
try {
const abi = getAbi(TOKEN_TYPE.ERC1155);
Expand All @@ -117,7 +166,102 @@ async function getERCInfo(ercAddress, ethereumAddress, provider, options) {
throw new Error('Unknown token type', ercAddress);
}
}
} */
}

/**
Get tokenIds in ethereum address for specific ERC721 or ERC1155 contract.
* @param {string} ercAddress - ERC contract address
* @param {string} ethereumAddress - The Ethereum address token owner
* @param {object} provider - web3 provider
* @returns {Object} {tokenType, balance, tokenIds} List of tokenId owned by the ethereum address in this ERC721 / ERC1155 contract
*/
async function getTokensInfo(ercAddress, ethereumAddress, provider) {
let balance;
let tokenType = TOKEN_TYPE.ERC20;
const tokenIds = [];

try {
// Check supportsInterface ERC165 that implements ERC721 and ERC1155
const abi = getAbi(TOKEN_TYPE.ERC165);
const ercContract = new provider.eth.Contract(abi, ercAddress);
const interface721 = await ercContract.methods.supportsInterface('0x80ac58cd').call(); // ERC721 interface
if (interface721) {
tokenType = TOKEN_TYPE.ERC721;
} else {
const interface1155 = await ercContract.methods.supportsInterface('0xd9b67a26').call(); // ERC1155 interface
if (interface1155) tokenType = TOKEN_TYPE.ERC1155;
}
} catch {
// TODO
throw new Error('Unknown token type (not ERC721 nor ERC1155)', ercAddress);
}

if (tokenType === TOKEN_TYPE.ERC1155) {
const abi = getAbi(TOKEN_TYPE.ERC1155);
const ercContract = new provider.eth.Contract(abi, ercAddress);
const tokenId = 1;

balance = await ercContract.methods.balanceOf(ethereumAddress, tokenId).call();
tokenType = TOKEN_TYPE.ERC1155;
const incomingTokenTransferBatchEvents = await ercContract.getPastEvents('TransferBatch', {
filter: {},
fromBlock: 0,
toBlock: 'latest',
});
const tokenIdsEvents = [];
incomingTokenTransferBatchEvents.forEach(event => {
event.returnValues.ids.forEach(id => {
if (!tokenIdsEvents.includes(id)) tokenIdsEvents.push(id);
});
});

const incomingTokenTransferSingleEvents = await ercContract.getPastEvents('TransferSingle', {
filter: {},
fromBlock: 0,
toBlock: 'latest',
});
incomingTokenTransferSingleEvents.forEach(event => {
if (!tokenIdsEvents.includes(event.returnValues.id))
tokenIdsEvents.push(event.returnValues.id);
});

await Promise.all(
tokenIdsEvents.map(async Id => {
const amount = await ercContract.methods.balanceOf(ethereumAddress, Id).call();
tokenIds.push({ Id, amount });
}),
);

balance = tokenIds
.reduce((partialSum, tokenIdInfo) => partialSum + BigInt(tokenIdInfo.amount), BigInt(0))
.toString();
} else if (tokenType === TOKEN_TYPE.ERC721) {
const abi = getAbi(TOKEN_TYPE.ERC721);
const ercContract = new provider.eth.Contract(abi, ercAddress);
balance = await ercContract.methods.balanceOf(ethereumAddress).call();
tokenType = TOKEN_TYPE.ERC721;

const incomingTokenTransferEvents = await ercContract.getPastEvents('Transfer', {
filter: { to: ethereumAddress },
fromBlock: 0,
toBlock: 'latest',
});
const tokenIdsEvents = [];
incomingTokenTransferEvents.forEach(event => {
if (!tokenIdsEvents.includes(event.returnValues.tokenId))
tokenIdsEvents.push(event.returnValues.tokenId);
});

await Promise.all(
tokenIdsEvents.map(async tokenId => {
const ownerTokenId = await ercContract.methods.ownerOf(tokenId).call();
if (ownerTokenId === ethereumAddress) tokenIds.push({ tokenId, amount: '1' });
}),
);
}

return { tokenType, balance, tokenIds };
}

export { approve, getDecimals, getERCInfo };
export { approve, getDecimals, getERCInfo, getTokensInfo };
13 changes: 12 additions & 1 deletion test/e2e/ercmocks.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import chai from 'chai';
import chaiHttp from 'chai-http';
import chaiAsPromised from 'chai-as-promised';
import Nf3 from '../../cli/lib/nf3.mjs';
import { getTokensInfo } from '../../cli/lib/tokens.mjs';
import {
connectWeb3,
closeWeb3Connection,
Expand Down Expand Up @@ -130,6 +131,10 @@ describe('Testing the Nightfall ERCMocks', () => {
` Balance ERC721Mock after transfer tokenId ${tokenId} [${nf3.ethereumAddress}]: ${balanceAfter}`,
);
expect(Number(balanceAfter)).to.be.equal(Number(balanceBefore) - 1);
const tokensInfo = await getTokensInfo(erc721Address, nf3.ethereumAddress, web3);
expect(Number(tokensInfo.balance)).to.be.equal(Number(balanceAfter));
expect(Number(tokensInfo.tokenIds.length)).to.be.equal(Number(balanceAfter));
console.log(`TOKENS INFO ${nf3.ethereumAddress} (${erc721Address}): `, tokensInfo);

await nf3.setEthereumSigningKey(walletTestSigningkey);
txDataToSign = await erc721Token.methods
Expand All @@ -153,9 +158,15 @@ describe('Testing the Nightfall ERCMocks', () => {
});

it('should get the balance of the ERC1155 contract mock', async function () {
const Id = 1; // Index Id from ERC1155 to check
const erc1155Token = new web3.eth.Contract(nf3.contracts.ERC1155, erc1155Address);
const balance = await erc1155Token.methods.balanceOf(nf3.ethereumAddress, 1).call();
const balance = await erc1155Token.methods.balanceOf(nf3.ethereumAddress, Id).call();
expect(Number(balance)).to.be.greaterThan(0);
const tokensInfo = await getTokensInfo(erc1155Address, nf3.ethereumAddress, web3);
expect(Number(balance)).to.be.equal(
Number(tokensInfo.tokenIds.find(tokenId => tokenId.Id === Id.toString()).amount),
);
console.log(`TOKENS INFO ${nf3.ethereumAddress} (${erc1155Address}): `, tokensInfo);
});

it('should get the balance of wallet address of the ERC1155 contract mock', async function () {
Expand Down

0 comments on commit 95c1ca9

Please sign in to comment.