@learncard/ethereum-plugin 1.1.24 → 1.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAa,MAAM,SAAS,CAAC;AASpE,cAAc,SAAS,CAAC;AAIxB;;GAEG;AACH,eAAO,MAAM,iBAAiB,kBACX,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,UAC3B,cAAc,KACvB,cAoKF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAa,MAAM,SAAS,CAAC;AAWpE,cAAc,SAAS,CAAC;AAMxB;;GAEG;AACH,eAAO,MAAM,iBAAiB,kBACX,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,UAC3B,cAAc,KACvB,cA+JF,CAAC"}
package/package.json CHANGED
@@ -1,57 +1,59 @@
1
1
  {
2
- "name": "@learncard/ethereum-plugin",
3
- "version": "1.1.24",
4
- "description": "",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/ethereum-plugin.esm.js",
8
- "exports": {
9
- ".": {
10
- "import": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/ethereum-plugin.esm.js"
13
- },
14
- "require": {
15
- "types": "./dist/index.d.cts",
16
- "default": "./dist/index.cjs"
17
- }
18
- }
19
- },
20
- "files": [
21
- "dist"
22
- ],
23
- "author": "Learning Economy Foundation (www.learningeconomy.io)",
24
- "license": "MIT",
25
- "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/plugins/ethereum/README.md",
26
- "repository": {
27
- "type": "git",
28
- "url": "https://github.com/learningeconomy/LearnCard"
29
- },
30
- "bugs": {
31
- "url": "https://github.com/learningeconomy/LearnCard/issues"
32
- },
33
- "devDependencies": {
34
- "@types/jest": "^29.2.2",
35
- "@types/node": "^17.0.31",
36
- "aqu": "0.4.3",
37
- "esbuild": "^0.27.1",
38
- "esbuild-jest": "^0.5.0",
39
- "esbuild-plugin-copy": "^1.3.0",
40
- "jest": "^29.3.0",
41
- "shx": "^0.3.4",
42
- "ts-jest": "^29.0.3"
43
- },
44
- "types": "./dist/index.d.ts",
45
- "dependencies": {
46
- "@uniswap/default-token-list": "^4.1.0",
47
- "ethers": "^5.6.9",
48
- "@learncard/core": "9.4.23"
49
- },
50
- "sideEffects": false,
51
- "scripts": {
52
- "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
53
- "test": "jest --passWithNoTests",
54
- "test:watch": "jest --watch",
55
- "test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
56
- }
57
- }
2
+ "name": "@learncard/ethereum-plugin",
3
+ "version": "1.1.26",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/ethereum-plugin.esm.js",
8
+ "exports": {
9
+ ".": {
10
+ "development": "./src/index.ts",
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/ethereum-plugin.esm.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "scripts": {
26
+ "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
27
+ "test": "jest --passWithNoTests",
28
+ "test:watch": "jest --watch",
29
+ "test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
30
+ },
31
+ "author": "Learning Economy Foundation (www.learningeconomy.io)",
32
+ "license": "MIT",
33
+ "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/plugins/ethereum/README.md",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/learningeconomy/LearnCard"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/learningeconomy/LearnCard/issues"
40
+ },
41
+ "devDependencies": {
42
+ "@types/jest": "^29.2.2",
43
+ "@types/node": "^17.0.31",
44
+ "aqu": "0.4.3",
45
+ "esbuild": "^0.27.1",
46
+ "esbuild-jest": "^0.5.0",
47
+ "esbuild-plugin-copy": "^1.3.0",
48
+ "jest": "^29.3.0",
49
+ "shx": "^0.3.4",
50
+ "ts-jest": "^29.0.3"
51
+ },
52
+ "types": "./dist/index.d.ts",
53
+ "dependencies": {
54
+ "@learncard/core": "workspace:*",
55
+ "@uniswap/default-token-list": "^4.1.0",
56
+ "ethers": "^5.6.9"
57
+ },
58
+ "sideEffects": false
59
+ }
@@ -0,0 +1,222 @@
1
+ [
2
+ {
3
+ "constant": true,
4
+ "inputs": [],
5
+ "name": "name",
6
+ "outputs": [
7
+ {
8
+ "name": "",
9
+ "type": "string"
10
+ }
11
+ ],
12
+ "payable": false,
13
+ "stateMutability": "view",
14
+ "type": "function"
15
+ },
16
+ {
17
+ "constant": false,
18
+ "inputs": [
19
+ {
20
+ "name": "_spender",
21
+ "type": "address"
22
+ },
23
+ {
24
+ "name": "_value",
25
+ "type": "uint256"
26
+ }
27
+ ],
28
+ "name": "approve",
29
+ "outputs": [
30
+ {
31
+ "name": "",
32
+ "type": "bool"
33
+ }
34
+ ],
35
+ "payable": false,
36
+ "stateMutability": "nonpayable",
37
+ "type": "function"
38
+ },
39
+ {
40
+ "constant": true,
41
+ "inputs": [],
42
+ "name": "totalSupply",
43
+ "outputs": [
44
+ {
45
+ "name": "",
46
+ "type": "uint256"
47
+ }
48
+ ],
49
+ "payable": false,
50
+ "stateMutability": "view",
51
+ "type": "function"
52
+ },
53
+ {
54
+ "constant": false,
55
+ "inputs": [
56
+ {
57
+ "name": "_from",
58
+ "type": "address"
59
+ },
60
+ {
61
+ "name": "_to",
62
+ "type": "address"
63
+ },
64
+ {
65
+ "name": "_value",
66
+ "type": "uint256"
67
+ }
68
+ ],
69
+ "name": "transferFrom",
70
+ "outputs": [
71
+ {
72
+ "name": "",
73
+ "type": "bool"
74
+ }
75
+ ],
76
+ "payable": false,
77
+ "stateMutability": "nonpayable",
78
+ "type": "function"
79
+ },
80
+ {
81
+ "constant": true,
82
+ "inputs": [],
83
+ "name": "decimals",
84
+ "outputs": [
85
+ {
86
+ "name": "",
87
+ "type": "uint8"
88
+ }
89
+ ],
90
+ "payable": false,
91
+ "stateMutability": "view",
92
+ "type": "function"
93
+ },
94
+ {
95
+ "constant": true,
96
+ "inputs": [
97
+ {
98
+ "name": "_owner",
99
+ "type": "address"
100
+ }
101
+ ],
102
+ "name": "balanceOf",
103
+ "outputs": [
104
+ {
105
+ "name": "balance",
106
+ "type": "uint256"
107
+ }
108
+ ],
109
+ "payable": false,
110
+ "stateMutability": "view",
111
+ "type": "function"
112
+ },
113
+ {
114
+ "constant": true,
115
+ "inputs": [],
116
+ "name": "symbol",
117
+ "outputs": [
118
+ {
119
+ "name": "",
120
+ "type": "string"
121
+ }
122
+ ],
123
+ "payable": false,
124
+ "stateMutability": "view",
125
+ "type": "function"
126
+ },
127
+ {
128
+ "constant": false,
129
+ "inputs": [
130
+ {
131
+ "name": "_to",
132
+ "type": "address"
133
+ },
134
+ {
135
+ "name": "_value",
136
+ "type": "uint256"
137
+ }
138
+ ],
139
+ "name": "transfer",
140
+ "outputs": [
141
+ {
142
+ "name": "",
143
+ "type": "bool"
144
+ }
145
+ ],
146
+ "payable": false,
147
+ "stateMutability": "nonpayable",
148
+ "type": "function"
149
+ },
150
+ {
151
+ "constant": true,
152
+ "inputs": [
153
+ {
154
+ "name": "_owner",
155
+ "type": "address"
156
+ },
157
+ {
158
+ "name": "_spender",
159
+ "type": "address"
160
+ }
161
+ ],
162
+ "name": "allowance",
163
+ "outputs": [
164
+ {
165
+ "name": "",
166
+ "type": "uint256"
167
+ }
168
+ ],
169
+ "payable": false,
170
+ "stateMutability": "view",
171
+ "type": "function"
172
+ },
173
+ {
174
+ "payable": true,
175
+ "stateMutability": "payable",
176
+ "type": "fallback"
177
+ },
178
+ {
179
+ "anonymous": false,
180
+ "inputs": [
181
+ {
182
+ "indexed": true,
183
+ "name": "owner",
184
+ "type": "address"
185
+ },
186
+ {
187
+ "indexed": true,
188
+ "name": "spender",
189
+ "type": "address"
190
+ },
191
+ {
192
+ "indexed": false,
193
+ "name": "value",
194
+ "type": "uint256"
195
+ }
196
+ ],
197
+ "name": "Approval",
198
+ "type": "event"
199
+ },
200
+ {
201
+ "anonymous": false,
202
+ "inputs": [
203
+ {
204
+ "indexed": true,
205
+ "name": "from",
206
+ "type": "address"
207
+ },
208
+ {
209
+ "indexed": true,
210
+ "name": "to",
211
+ "type": "address"
212
+ },
213
+ {
214
+ "indexed": false,
215
+ "name": "value",
216
+ "type": "uint256"
217
+ }
218
+ ],
219
+ "name": "Transfer",
220
+ "type": "event"
221
+ }
222
+ ]
@@ -0,0 +1,26 @@
1
+ export const hardcodedTokens = [
2
+ {
3
+ name: 'USDCoin',
4
+ address: '0x07865c6e87b9f70255377e024ace6630c1eaa37f',
5
+ symbol: 'USDC',
6
+ decimals: 6,
7
+ chainId: 5,
8
+ logoURI:
9
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
10
+ extensions: {
11
+ bridgeInfo: {
12
+ '10': {
13
+ tokenAddress: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
14
+ },
15
+ '137': {
16
+ tokenAddress: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
17
+ },
18
+ '42161': {
19
+ tokenAddress: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
20
+ },
21
+ },
22
+ },
23
+ },
24
+ ];
25
+
26
+ export default hardcodedTokens;
package/src/helpers.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { ethers } from 'ethers';
2
+
3
+ import { Token, TokenList } from './types';
4
+
5
+ export const isAddress = (maybeAddress: string) => {
6
+ return maybeAddress.startsWith('0x') && maybeAddress.length === 42;
7
+ };
8
+
9
+ /* From Ethereum-compatible numbers to human-readable numbers */
10
+ export const formatUnits = async (
11
+ units: ethers.BigNumberish,
12
+ symbolOrAddress: string,
13
+ tokenList: TokenList,
14
+ chainId: number
15
+ ) => {
16
+ const token = await getTokenFromSymbolOrAddress(symbolOrAddress, tokenList, chainId);
17
+
18
+ return ethers.utils.formatUnits(units, token?.decimals);
19
+ };
20
+
21
+ /* From human-readable numbers to Ethereum-compatible numbers */
22
+ export const parseUnits = async (
23
+ units: string,
24
+ symbolOrAddress: string,
25
+ tokenList: TokenList,
26
+ chainId: number
27
+ ) => {
28
+ const token = await getTokenFromSymbolOrAddress(symbolOrAddress, tokenList, chainId);
29
+
30
+ return ethers.utils.parseUnits(units, token?.decimals);
31
+ };
32
+
33
+ export const getTokenFromSymbolOrAddress = async (
34
+ symbolOrAddress: string,
35
+ tokenList: TokenList,
36
+ chainId: number
37
+ ): Promise<Token | undefined> => {
38
+ let token;
39
+ if (isAddress(symbolOrAddress)) {
40
+ token = await getTokenFromAddress(symbolOrAddress, tokenList, chainId);
41
+ } else {
42
+ token = await getTokenFromSymbol(symbolOrAddress, tokenList, chainId);
43
+ }
44
+ return token;
45
+ };
46
+
47
+ const getTokenFromAddress = async (address: string, tokenList: TokenList, chainId: number) => {
48
+ const token = tokenList.find(token => token.chainId === chainId && token.address === address);
49
+
50
+ return token;
51
+ };
52
+
53
+ const getTokenFromSymbol = async (symbol: string, tokenList: TokenList, chainId: number) => {
54
+ const token = tokenList.find(
55
+ token => token.chainId === chainId && token.symbol.toUpperCase() === symbol.toUpperCase()
56
+ );
57
+
58
+ return token;
59
+ };
60
+
61
+ export const getChainIdFromProvider = async (provider: ethers.providers.Provider) => {
62
+ return (await provider.getNetwork()).chainId;
63
+ };
package/src/index.ts ADDED
@@ -0,0 +1,188 @@
1
+ import { Buffer } from 'buffer';
2
+
3
+ import { LearnCard } from '@learncard/core';
4
+ import { ethers } from 'ethers';
5
+
6
+ import { EthereumConfig, EthereumPlugin, TokenList } from './types';
7
+ import {
8
+ formatUnits,
9
+ parseUnits,
10
+ getTokenFromSymbolOrAddress,
11
+ getChainIdFromProvider,
12
+ } from './helpers';
13
+ import ERC20ABI from './erc20.abi.json';
14
+ import uniswapDefaultTokenList from '@uniswap/default-token-list/build/uniswap-default.tokenlist.json';
15
+ import hardcodedTokens from './hardcodedTokens';
16
+
17
+ export * from './types';
18
+
19
+ const defaultTokenList = (uniswapDefaultTokenList as { tokens: TokenList }).tokens.concat(
20
+ hardcodedTokens
21
+ );
22
+
23
+ /**
24
+ * @group Plugins
25
+ */
26
+ export const getEthereumPlugin = (
27
+ initLearnCard: LearnCard<any, 'id'>,
28
+ config: EthereumConfig
29
+ ): EthereumPlugin => {
30
+ let { infuraProjectId, network = 'mainnet' } = config;
31
+
32
+ // Ethers wallet
33
+ const secpKeypair = initLearnCard.id.keypair('secp256k1');
34
+
35
+ if (!secpKeypair) {
36
+ throw new Error('LearnCard must support secp256k1 JWK in order to add Ethereum Plugin');
37
+ }
38
+
39
+ const privateKey = Buffer.from(secpKeypair.d, 'base64').toString('hex');
40
+ let ethersWallet = new ethers.Wallet(privateKey);
41
+ const publicKey: string = ethersWallet.address;
42
+
43
+ // Provider
44
+ const getProvider = () => {
45
+ let provider: ethers.providers.Provider;
46
+ if (infuraProjectId) {
47
+ provider = new ethers.providers.InfuraProvider(network, infuraProjectId);
48
+ } else {
49
+ provider = ethers.getDefaultProvider(network);
50
+ }
51
+
52
+ ethersWallet = ethersWallet.connect(provider);
53
+ return provider;
54
+ };
55
+ let provider: ethers.providers.Provider;
56
+
57
+ const defaultTokenListForNetwork = defaultTokenList;
58
+
59
+ const getTokenAddress = async (tokenSymbolOrAddress: string) => {
60
+ if (!provider) provider = getProvider();
61
+
62
+ return (
63
+ await getTokenFromSymbolOrAddress(
64
+ tokenSymbolOrAddress,
65
+ defaultTokenListForNetwork,
66
+ await getChainIdFromProvider(provider)
67
+ )
68
+ )?.address;
69
+ };
70
+
71
+ const getErc20TokenBalance = async (
72
+ tokenContractAddress: string,
73
+ walletPublicAddress = publicKey
74
+ ) => {
75
+ if (!provider) provider = getProvider();
76
+
77
+ const contract = new ethers.Contract(tokenContractAddress, ERC20ABI, provider);
78
+
79
+ const balance = await contract.balanceOf(walletPublicAddress);
80
+ const formattedBalance = formatUnits(
81
+ balance,
82
+ tokenContractAddress,
83
+ defaultTokenList,
84
+ await getChainIdFromProvider(provider)
85
+ );
86
+
87
+ return formattedBalance;
88
+ };
89
+
90
+ // Core Methods
91
+ const getBalance = async (walletAddress = publicKey, tokenSymbolOrAddress = 'ETH') => {
92
+ if (!provider) provider = getProvider();
93
+
94
+ if (!tokenSymbolOrAddress || tokenSymbolOrAddress === 'ETH') {
95
+ // check ETH by default
96
+ const balance = await provider.getBalance(walletAddress);
97
+ const formattedBalance = ethers.utils.formatEther(balance);
98
+
99
+ return formattedBalance;
100
+ }
101
+
102
+ const tokenAddress = await getTokenAddress(tokenSymbolOrAddress);
103
+
104
+ if (!tokenAddress) {
105
+ throw new Error(`Unable to determine token address for \"${tokenSymbolOrAddress}\"`);
106
+ }
107
+
108
+ const balance = await getErc20TokenBalance(tokenAddress, walletAddress);
109
+
110
+ return balance;
111
+ };
112
+
113
+ return {
114
+ name: 'Ethereum',
115
+ displayName: 'Ethereum',
116
+ description: 'Provides access to currency',
117
+ methods: {
118
+ getEthereumAddress: () => publicKey,
119
+
120
+ getBalance: async (_learnCard, symbolOrAddress = 'ETH') =>
121
+ getBalance(publicKey, symbolOrAddress),
122
+ getBalanceForAddress: async (_learnCard, walletAddress, symbolOrAddress) =>
123
+ getBalance(walletAddress, symbolOrAddress),
124
+
125
+ transferTokens: async (_learnCard, tokenSymbolOrAddress, amount, toAddress) => {
126
+ if (!provider) provider = getProvider();
127
+
128
+ if (tokenSymbolOrAddress === 'ETH') {
129
+ const transaction = {
130
+ to: toAddress,
131
+
132
+ // Convert ETH to wei
133
+ value: ethers.utils.parseEther(amount.toString()),
134
+ };
135
+
136
+ return (await ethersWallet.sendTransaction(transaction)).hash;
137
+ }
138
+
139
+ const tokenAddress = await getTokenAddress(tokenSymbolOrAddress);
140
+ if (!tokenAddress) {
141
+ throw new Error(
142
+ `Unable to determine token address for \"${tokenSymbolOrAddress}\"`
143
+ );
144
+ }
145
+
146
+ const tokenContract = new ethers.Contract(tokenAddress, ERC20ABI, ethersWallet);
147
+
148
+ return (
149
+ await tokenContract.transfer(
150
+ toAddress,
151
+ await parseUnits(
152
+ amount.toString(),
153
+ tokenContract.address,
154
+ defaultTokenList,
155
+ await getChainIdFromProvider(provider)
156
+ )
157
+ )
158
+ ).hash;
159
+ },
160
+
161
+ getGasPrice: async () => {
162
+ if (!provider) provider = getProvider();
163
+
164
+ return ethers.utils.formatUnits(await provider.getGasPrice());
165
+ },
166
+
167
+ /* Configuration-type methods */
168
+ getCurrentNetwork: () => {
169
+ return network;
170
+ },
171
+ changeNetwork: (_learnCard, _network: ethers.providers.Networkish) => {
172
+ const oldNetwork = network;
173
+ try {
174
+ network = _network;
175
+ provider = getProvider();
176
+ } catch (e) {
177
+ network = oldNetwork;
178
+ provider = getProvider();
179
+ throw e;
180
+ }
181
+ },
182
+ addInfuraProjectId: (_learnCard, infuraProjectIdToAdd) => {
183
+ infuraProjectId = infuraProjectIdToAdd;
184
+ provider = getProvider();
185
+ },
186
+ },
187
+ };
188
+ };
package/src/types.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { providers } from 'ethers';
2
+ import { Plugin } from '@learncard/core';
3
+
4
+ /** @group Ethereum Plugin */
5
+ export type EthereumPluginMethods = {
6
+ getEthereumAddress: () => string; // TODO is there a way for this to just be a property?
7
+ getBalance: (symbolOrAddress?: string) => Promise<string>;
8
+ getBalanceForAddress: (walletAddress: string, symbolOrAddress?: string) => Promise<string>;
9
+ transferTokens: (
10
+ tokenSymbolOrAddress: string,
11
+ amount: number,
12
+ toAddress: string
13
+ ) => Promise<string>;
14
+ getGasPrice: () => Promise<string>;
15
+ getCurrentNetwork: () => providers.Networkish;
16
+ changeNetwork: (network: providers.Networkish) => void;
17
+ addInfuraProjectId: (infuraProjectIdToAdd: string) => void;
18
+ };
19
+
20
+ /** @group Ethereum Plugin */
21
+ export type EthereumConfig = {
22
+ infuraProjectId?: string;
23
+ network?: providers.Networkish;
24
+ };
25
+
26
+ /** @group Ethereum Plugin */
27
+ export type Token = {
28
+ chainId: number;
29
+ address: string;
30
+ name: string;
31
+ symbol: string;
32
+ decimals: number;
33
+ logoURI: string;
34
+ extensions: any;
35
+ };
36
+
37
+ /** @group Ethereum Plugin */
38
+ export type TokenList = Token[];
39
+
40
+ /** @group Ethereum Plugin */
41
+ export type EthereumPlugin = Plugin<'Ethereum', any, EthereumPluginMethods, 'id'>;