Skip to content

Commit 75113c8

Browse files
authored
Merge pull request #122 from magiclabs/bengriffin1-add-token-gating
Adding type and abis
2 parents ddc0194 + 78dc282 commit 75113c8

File tree

15 files changed

+2950
-888
lines changed

15 files changed

+2950
-888
lines changed

.eslintrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
"no-cond-assign": 0,
1515
"class-methods-use-this": 0,
1616
"no-underscore-dangle": 0,
17-
"no-useless-constructor": 0
17+
"no-useless-constructor": 0,
18+
// Note: you must disable the base rule as it can report incorrect errors
19+
"no-shadow": 0,
20+
"@typescript-eslint/no-shadow": "warn",
21+
"no-empty-function": 0
1822
},
1923
"settings": {
2024
"import/resolver": {

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
20.10

package.json

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,38 +29,39 @@
2929
"clean_node_modules": "rimraf node_modules"
3030
},
3131
"devDependencies": {
32-
"@ikscodes/eslint-config": "^6.2.0",
33-
"@ikscodes/prettier-config": "^1.0.0",
32+
"@ikscodes/eslint-config": "^8.4.1",
3433
"@istanbuljs/nyc-config-typescript": "^1.0.1",
3534
"@types/jest": "^27.4.1",
3635
"@types/node": "^13.1.2",
3736
"@types/node-fetch": "^2.5.4",
38-
"@typescript-eslint/eslint-plugin": "^2.15.0",
39-
"auto": "^9.60.1",
37+
"@typescript-eslint/eslint-plugin": "^6.21.0",
38+
"auto": "11.0.5",
4039
"boxen-cli": "^1.0.0",
4140
"esbuild": "^0.14.54",
42-
"eslint": "^6.7.2",
41+
"eslint": "^8.56.0",
4342
"eslint-import-resolver-typescript": "^2.0.0",
44-
"eslint-plugin-import": "^2.18.2",
45-
"eslint-plugin-jsx-a11y": "^6.2.3",
46-
"eslint-plugin-prettier": "^3.1.2",
47-
"eslint-plugin-react": "^7.15.1",
48-
"eslint-plugin-react-hooks": "^1.7.0",
49-
"husky": "^4.2.3",
43+
"eslint-plugin-import": "^2.29.1",
44+
"eslint-plugin-jsx-a11y": "^6.8.0",
45+
"eslint-plugin-prettier": "^5.1.3",
46+
"husky": "^8.0.3",
5047
"jest": "^27.5.1",
5148
"lint-staged": "^10.0.8",
5249
"npm-run-all": "~4.1.5",
53-
"prettier": "^1.19.1",
50+
"prettier": "^3.2.4",
5451
"rimraf": "~3.0.0",
5552
"ts-jest": "^27.1.3",
56-
"ts-node": "~8.5.2",
53+
"ts-node": "^10.2.0",
5754
"tslint": "~5.20.1",
58-
"typescript": "~3.8.3"
55+
"typescript": "^5.3.3",
56+
"web3": "^4.6.0"
5957
},
6058
"dependencies": {
6159
"ethereum-cryptography": "^1.0.1",
6260
"node-fetch": "^2.6.7"
6361
},
62+
"peerDependencies": {
63+
"web3": "^4.6.0"
64+
},
6465
"husky": {
6566
"hooks": {
6667
"pre-commit": "lint-staged"

src/core/sdk-exceptions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { ErrorCode } from '../types';
55
export class MagicAdminSDKError extends Error {
66
__proto__ = Error;
77

8-
constructor(public code: ErrorCode, message: string, public data: any[] = []) {
8+
constructor(
9+
public code: ErrorCode,
10+
message: string,
11+
public data: any[] = [],
12+
) {
913
super(`Magic Admin SDK Error: [${code}] ${message}`);
1014
Object.setPrototypeOf(this, MagicAdminSDKError.prototype);
1115
}

src/core/sdk.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { createApiKeyMissingError } from './sdk-exceptions';
12
import { TokenModule } from '../modules/token';
23
import { UsersModule } from '../modules/users';
34
import { UtilsModule } from '../modules/utils';
45
import { MagicAdminSDKAdditionalConfiguration } from '../types';
56
import { get } from '../utils/rest';
6-
import { createApiKeyMissingError } from './sdk-exceptions';
77

88
export class MagicAdminSDK {
99
public readonly apiBaseUrl: string;
@@ -35,7 +35,10 @@ export class MagicAdminSDK {
3535
* @param secretApiKey
3636
* @param options
3737
*/
38-
constructor(public readonly secretApiKey?: string, options?: MagicAdminSDKAdditionalConfiguration) {
38+
constructor(
39+
public readonly secretApiKey?: string,
40+
options?: MagicAdminSDKAdditionalConfiguration,
41+
) {
3942
const endpoint = options?.endpoint ?? 'https://api.magic.link';
4043
this.apiBaseUrl = endpoint.replace(/\/+$/, '');
4144
this.clientId = options?.clientId ?? null;

src/modules/utils/index.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import Web3 from 'web3';
12
import { BaseModule } from '../base-module';
2-
import { createExpectedBearerStringError } from '../../core/sdk-exceptions';
3+
import {createExpectedBearerStringError} from '../../core/sdk-exceptions';
4+
import { ValidateTokenOwnershipResponse } from '../../types';
5+
import { ERC1155ContractABI, ERC721ContractABI } from './ownershipABIs';
6+
import { ErrorCode } from '../../types';
37

48
export class UtilsModule extends BaseModule {
59
/**
@@ -12,4 +16,64 @@ export class UtilsModule extends BaseModule {
1216

1317
return header.substring(7);
1418
}
19+
20+
// Token Gating function validates user ownership of wallet + NFT
21+
public async validateTokenOwnership(
22+
didToken: string,
23+
contractAddress: string,
24+
contractType: 'ERC721' | 'ERC1155',
25+
web3: Web3,
26+
tokenId?: string,
27+
): Promise<ValidateTokenOwnershipResponse> {
28+
// Make sure if ERC1155 has a tokenId
29+
if (contractType === 'ERC1155' && !tokenId) {
30+
throw new Error('ERC1155 requires a tokenId');
31+
}
32+
// Validate DID token
33+
let walletAddress;
34+
try {
35+
await this.sdk.token.validate(didToken);
36+
walletAddress = this.sdk.token.getPublicAddress(didToken);
37+
} catch (e: any) {
38+
// Check if code is malformed token
39+
if (e.code && e.code === 'ERROR_MALFORMED_TOKEN') {
40+
return {
41+
valid: false,
42+
error_code: 'UNAUTHORIZED',
43+
message: 'Invalid DID token: ' + ErrorCode.MalformedTokenError,
44+
};
45+
}
46+
if (e.code === ErrorCode.TokenExpired) {
47+
return {
48+
valid: false,
49+
error_code: 'UNAUTHORIZED',
50+
message: 'Invalid DID token: ' + ErrorCode.TokenExpired,
51+
};
52+
}
53+
throw new Error(e);
54+
}
55+
56+
57+
// Check on-chain if user owns NFT by calling contract with web3
58+
let balance = BigInt(0);
59+
if (contractType === 'ERC721') {
60+
const contract = new web3.eth.Contract(ERC721ContractABI, contractAddress);
61+
balance = BigInt(await contract.methods.balanceOf(walletAddress).call());
62+
} else {
63+
const contract = new web3.eth.Contract(ERC1155ContractABI, contractAddress);
64+
balance = BigInt(await contract.methods.balanceOf(walletAddress, tokenId).call());
65+
}
66+
if (balance > BigInt(0)) {
67+
return {
68+
valid: true,
69+
error_code: '',
70+
message: '',
71+
};
72+
}
73+
return {
74+
valid: false,
75+
error_code: 'NO_OWNERSHIP',
76+
message: 'User does not own this token.',
77+
};
78+
}
1579
}

src/modules/utils/ownershipABIs.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Reduced ABI for ERC1155 with just balanceOf
2+
export const ERC1155ContractABI = [
3+
{
4+
"constant": true,
5+
"inputs": [
6+
{
7+
"name": "_owner",
8+
"type": "address"
9+
},
10+
{
11+
"name": "_id",
12+
"type": "uint256"
13+
}
14+
],
15+
"name": "balanceOf",
16+
"outputs": [
17+
{
18+
"name": "",
19+
"type": "uint256"
20+
}
21+
],
22+
"payable": false,
23+
"stateMutability": "view",
24+
"type": "function"
25+
}
26+
];
27+
28+
// Reduced ABI for ERC721 with just balanceOf
29+
export const ERC721ContractABI = [
30+
{
31+
"constant": true,
32+
"inputs": [
33+
{
34+
"name": "_owner",
35+
"type": "address"
36+
},
37+
{
38+
"name": "_id",
39+
"type": "uint256"
40+
}
41+
],
42+
"name": "balanceOf",
43+
"outputs": [
44+
{
45+
"name": "",
46+
"type": "uint256"
47+
}
48+
],
49+
"payable": false,
50+
"stateMutability": "view",
51+
"type": "function"
52+
}
53+
];

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './didt-types';
22
export * from './exception-types';
33
export * from './sdk-types';
44
export * from './wallet-types';
5+
export * from './utils-types';

src/types/utils-types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface ValidateTokenOwnershipResponse {
2+
valid: boolean;
3+
error_code: string;
4+
message: string;
5+
}

src/utils/parse-didt.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Claim, ParsedDIDToken } from '../types';
1+
import { decodeValue } from './codec';
22
import { isDIDTClaim } from './type-guards';
33
import { createMalformedTokenError } from '../core/sdk-exceptions';
4-
import { decodeValue } from './codec';
4+
import { Claim, ParsedDIDToken } from '../types';
55

66
interface ParseDIDTokenResult {
77
raw: [string, string];

0 commit comments

Comments
 (0)