Skip to content

Commit

Permalink
Merge pull request #124 from magiclabs/bengriffin1-token-gating-updates
Browse files Browse the repository at this point in the history
Switching to use ethers and accept RPC url for token gating validation
  • Loading branch information
bengriffin1 committed Apr 4, 2024
2 parents 7e39f65 + 0e96947 commit f045acd
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
cache: 'yarn'
- run: |
yarn -v
Expand Down
2 changes: 1 addition & 1 deletion config/tsconfig.base.json
Expand Up @@ -3,7 +3,7 @@
"lib": ["es2018", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"target": "es6",
"strict": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
Expand Down
4 changes: 2 additions & 2 deletions config/tsconfig.sdk.esm.json
Expand Up @@ -2,8 +2,8 @@
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["es2020", "dom"],
"target": "esnext",
"module": "esnext",
"target": "es2020",
"module": "es2020",
"outDir": "../dist/esm"
},
"include": ["../src/**/*.ts"]
Expand Down
15 changes: 4 additions & 11 deletions package.json
Expand Up @@ -26,7 +26,8 @@
"clean": "npm-run-all -s clean:*",
"clean:test-artifacts": "rimraf coverage",
"clean:build": "rimraf dist",
"clean_node_modules": "rimraf node_modules"
"clean_node_modules": "rimraf node_modules",
"prepare": "husky install"
},
"devDependencies": {
"@ikscodes/eslint-config": "^8.4.1",
Expand All @@ -52,21 +53,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"
}
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix"
},
Expand Down
13 changes: 7 additions & 6 deletions src/modules/utils/index.ts
@@ -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,13 @@ 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);
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);
balance = BigInt(await contract.balanceOf(walletAddress, tokenId));
}
if (balance > BigInt(0)) {
return {
Expand Down
57 changes: 5 additions & 52 deletions src/modules/utils/ownershipABIs.ts
@@ -1,53 +1,6 @@
// 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"
}
];
"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
@@ -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
5 changes: 4 additions & 1 deletion test/tsconfig.json
@@ -1,3 +1,6 @@
{
"extends": "../config/tsconfig.test.json"
"extends": "../config/tsconfig.test.json",
"compilerOptions": {
"target": "es5"
}
}
5 changes: 4 additions & 1 deletion tsconfig.json
@@ -1,3 +1,6 @@
{
"extends": "./config/tsconfig.base.json"
"extends": "./config/tsconfig.base.json",
"compilerOptions": {
"target": "es5"
}
}

0 comments on commit f045acd

Please sign in to comment.