Skip to content

Commit

Permalink
Switching to use ethers and accept RPC url for token gating validation
Browse files Browse the repository at this point in the history
  • Loading branch information
bengriffin1 committed Apr 4, 2024
1 parent 7e39f65 commit 1673b36
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 143 deletions.
7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,13 @@
"ts-jest": "^27.1.3",
"ts-node": "^10.2.0",
"tslint": "~5.20.1",
"typescript": "^5.3.3",
"web3": "^4.6.0"
"typescript": "^5.3.3"
},
"dependencies": {
"ethereum-cryptography": "^1.0.1",
"ethers": "^6.11.1",
"node-fetch": "^2.6.7"
},
"peerDependencies": {
"web3": "^4.6.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
Expand Down
15 changes: 9 additions & 6 deletions src/modules/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Web3 from 'web3';
import { ethers } from "ethers";
import { BaseModule } from '../base-module';
import {createExpectedBearerStringError} from '../../core/sdk-exceptions';
import { ValidateTokenOwnershipResponse } from '../../types';
Expand All @@ -22,7 +22,7 @@ export class UtilsModule extends BaseModule {
didToken: string,
contractAddress: string,
contractType: 'ERC721' | 'ERC1155',
web3: Web3,
rpcURL: string,
tokenId?: string,
): Promise<ValidateTokenOwnershipResponse> {
// Make sure if ERC1155 has a tokenId
Expand Down Expand Up @@ -56,12 +56,15 @@ export class UtilsModule extends BaseModule {

// Check on-chain if user owns NFT by calling contract with web3
let balance = BigInt(0);
const provider = new ethers.JsonRpcProvider();
if (contractType === 'ERC721') {
const contract = new web3.eth.Contract(ERC721ContractABI, contractAddress);
balance = BigInt(await contract.methods.balanceOf(walletAddress).call());
const contract = new ethers.Contract(contractAddress, ERC721ContractABI, provider);
// const contract = new web3.eth.Contract(ERC721ContractABI, contractAddress);
balance = BigInt(await contract.balanceOf(walletAddress));
} else {
const contract = new web3.eth.Contract(ERC1155ContractABI, contractAddress);
balance = BigInt(await contract.methods.balanceOf(walletAddress, tokenId).call());
const contract = new ethers.Contract(contractAddress, ERC1155ContractABI, provider);
// const contract = new web3.eth.Contract(ERC1155ContractABI, contractAddress);
balance = BigInt(await contract.balanceOf(walletAddress, tokenId));
}
if (balance > BigInt(0)) {
return {
Expand Down
109 changes: 58 additions & 51 deletions src/modules/utils/ownershipABIs.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
// Reduced ABI for ERC1155 with just balanceOf
export const ERC1155ContractABI = [
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_id",
"type": "uint256"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
];
// // Reduced ABI for ERC1155 with just balanceOf
// export const ERC1155ContractABI = [
// {
// "constant": true,
// "inputs": [
// {
// "name": "_owner",
// "type": "address"
// },
// {
// "name": "_id",
// "type": "uint256"
// }
// ],
// "name": "balanceOf",
// "outputs": [
// {
// "name": "",
// "type": "uint256"
// }
// ],
// "payable": false,
// "stateMutability": "view",
// "type": "function"
// }
// ];

// // Reduced ABI for ERC721 with just balanceOf
// export const ERC721ContractABI = [
// {
// "constant": true,
// "inputs": [
// {
// "name": "_owner",
// "type": "address"
// },
// {
// "name": "_id",
// "type": "uint256"
// }
// ],
// "name": "balanceOf",
// "outputs": [
// {
// "name": "",
// "type": "uint256"
// }
// ],
// "payable": false,
// "stateMutability": "view",
// "type": "function"
// }
// ];

// Reduced ABI for ERC721 with just balanceOf
export const ERC721ContractABI = [
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_id",
"type": "uint256"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
];
"function balanceOf(address) view returns (uint)",
]
export const ERC1155ContractABI = [
"function balanceOf(address, id) view returns (uint)",
]
74 changes: 28 additions & 46 deletions test/spec/modules/utils/validateTokenOwnership.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import Web3 from 'web3';
import { createMagicAdminSDK } from '../../../lib/factories';

jest.mock('ethers', () => {
const originalModule = jest.requireActual('ethers');
return {
...originalModule,
ethers: {
...originalModule.ethers,
Contract: jest.fn(() => ({
balanceOf: jest.fn().mockImplementation((walletAddress: string, tokenId?: string) => {
if (tokenId === '2') {
return BigInt(1); // User owns token
} else {
return BigInt(0); // User doesn't own token
}
}),
})),
},
};
});


test('Throws an error if ERC1155 and no token provided', async () => {
const sdk = createMagicAdminSDK('https://example.com');
const web3 = new Web3('https://example.com');

await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', web3)).rejects.toThrow(
await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com')).rejects.toThrow(
'ERC1155 requires a tokenId',
);
});

test('Returns an error if DID token is malformed', async () => {
const sdk = createMagicAdminSDK('https://example.com');
const web3 = new Web3('https://example.com');

// Mock the magic token validation by setting the code to ERROR_MALFORMED_TOKEN
sdk.token.validate = jest.fn().mockRejectedValue({ code: 'ERROR_MALFORMED_TOKEN' });

await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', web3, '1')).resolves.toEqual({
await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com', '1')).resolves.toEqual({
valid: false,
error_code: 'UNAUTHORIZED',
message: 'Invalid DID token: ERROR_MALFORMED_TOKEN',
Expand All @@ -26,12 +43,11 @@ test('Returns an error if DID token is malformed', async () => {

test('Returns an error if DID token is expired', async () => {
const sdk = createMagicAdminSDK('https://example.com');
const web3 = new Web3('https://example.com');

// Mock the magic token validation by setting the code to ERROR_DIDT_EXPIRED
sdk.token.validate = jest.fn().mockRejectedValue({ code: 'ERROR_DIDT_EXPIRED' });

await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', web3, '1')).resolves.toEqual({
await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com', '1')).resolves.toEqual({
valid: false,
error_code: 'UNAUTHORIZED',
message: 'Invalid DID token: ERROR_DIDT_EXPIRED',
Expand All @@ -40,12 +56,11 @@ test('Returns an error if DID token is expired', async () => {

test('Throws an error if DID token validation returns unexpected error code', async () => {
const sdk = createMagicAdminSDK('https://example.com');
const web3 = new Web3('https://example.com');

// Mock the magic token validation by setting the code to ERROR_MALFORMED_TOKEN
sdk.token.validate = jest.fn().mockRejectedValue({ code: 'UNKNOWN' });

await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', web3, '1')).rejects.toThrow();
await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com', '1')).rejects.toThrow();
});

test('Returns an error if ERC721 token is not owned by user', async () => {
Expand All @@ -55,24 +70,13 @@ test('Returns an error if ERC721 token is not owned by user', async () => {
sdk.token.validate = jest.fn().mockResolvedValue({});
// Mock the getPublicAddress to return valid email and wallet
sdk.token.getPublicAddress = jest.fn().mockReturnValue('0x610dcb8fd5cf7f544b85290889a456916fbeaba2');
// Mock the web3 contract.methods.balanceOf to return 0
const callStub = jest.fn().mockResolvedValue(BigInt(0));
// Mock the contract instance
const contractMock: any = {
methods: {
balanceOf: jest.fn().mockReturnValue({ call: callStub }),
},
};
// Mock web3.eth.Contract
const web3 = new Web3('https://example.com');
jest.spyOn(web3.eth, 'Contract').mockReturnValue(contractMock);

await expect(
sdk.utils.validateTokenOwnership(
'did:ethr:0x123',
'0x610dcb8fd5cf7f544b85290889a456916fbeaba2',
'ERC721',
web3,
'https://example.com',
'1',
),
).resolves.toEqual({
Expand All @@ -89,24 +93,13 @@ test('Returns an error if ERC1155 token is not owned by user', async () => {
sdk.token.validate = jest.fn().mockResolvedValue({});
// Mock the getPublicAddress to return valid email and wallet
sdk.token.getPublicAddress = jest.fn().mockReturnValue('0x610dcb8fd5cf7f544b85290889a456916fbeaba2');
// Mock the web3 contract.methods.balanceOf to return 0
const callStub = jest.fn().mockResolvedValue(BigInt(0));
// Mock the contract instance
const contractMock: any = {
methods: {
balanceOf: jest.fn().mockReturnValue({ call: callStub }),
},
};
// Mock web3.eth.Contract
const web3 = new Web3('https://example.com');
jest.spyOn(web3.eth, 'Contract').mockReturnValue(contractMock);

await expect(
sdk.utils.validateTokenOwnership(
'did:ethr:0x123',
'0x610dcb8fd5cf7f544b85290889a456916fbeaba2',
'ERC1155',
web3,
'https://example.com',
'1',
),
).resolves.toEqual({
Expand All @@ -123,25 +116,14 @@ test('Returns success if ERC1155 token is owned by user', async () => {
sdk.token.validate = jest.fn().mockResolvedValue({});
// Mock the getPublicAddress to return valid email and wallet
sdk.token.getPublicAddress = jest.fn().mockReturnValue('0x610dcb8fd5cf7f544b85290889a456916fbeaba2');
// Mock the web3 contract.methods.balanceOf to return 0
const callStub = jest.fn().mockResolvedValue(BigInt(1));
// Mock the contract instance
const contractMock: any = {
methods: {
balanceOf: jest.fn().mockReturnValue({ call: callStub }),
},
};
// Mock web3.eth.Contract
const web3 = new Web3('https://example.com');
jest.spyOn(web3.eth, 'Contract').mockReturnValue(contractMock);

await expect(
sdk.utils.validateTokenOwnership(
'did:ethr:0x123',
'0x610dcb8fd5cf7f544b85290889a456916fbeaba2',
'ERC1155',
web3,
'1',
'https://example.com',
'2',
),
).resolves.toEqual({
valid: true,
Expand Down
9 changes: 8 additions & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
{
"extends": "../config/tsconfig.test.json"
"extends": "../config/tsconfig.test.json",
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
}
}
5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": "./config/tsconfig.base.json"
"extends": "./config/tsconfig.base.json",
"compilerOptions": {
"esModuleInterop": true,
}
}
Loading

0 comments on commit 1673b36

Please sign in to comment.