Skip to content

Commit 9158cd9

Browse files
authored
Merge pull request #36 from hgraph-io/erc
add erc20 support
2 parents 7acacc5 + e8377ae commit 9158cd9

File tree

9 files changed

+231
-0
lines changed

9 files changed

+231
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ This command initiates a WebSocket connection to the GraphQL endpoint at `wss://
171171
npm install -g wscat
172172
```
173173

174+
## Token Helpers (ERC-20 & ERC-721)
175+
176+
The SDK provides simple wrappers around standard token contracts using `ethers.js`.
177+
See [`src/tokens/README.md`](src/tokens/README.md) for full examples.
178+
174179
## Versioning
175180

176181
This Software Development Kit (SDK) is actively being developed in conjunction with the [Hgraph API](https://hgraph.com) to ensure seamless integration and compatibility between the two. We are committed to adopting [Semantic Versioning](https://semver.org) standards, which will provide clear and predictable updates, making it easier for developers to manage dependencies and stay informed about changes.

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export {default as default} from './client'
44
export {HgraphContract} from './contract'
55
export {useHgraph} from './hooks/useHgraph'
66
export {HgraphProvider} from './context/HgraphProvider'
7+
export * from './tokens'

src/tokens/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Token Helpers
2+
3+
The SDK ships with lightweight wrappers around standard ERC-20 and ERC-721 contracts. Each wrapper uses `ethers.js` under the hood and exposes common methods so you can easily integrate token functionality in your application.
4+
5+
## ERC-20
6+
7+
```typescript
8+
import {ERC20} from '@hgraph.io/sdk'
9+
import {JsonRpcProvider} from 'ethers'
10+
11+
const provider = new JsonRpcProvider('https://your.rpc.url')
12+
const token = new ERC20('0xTokenAddress', provider)
13+
14+
const balance = await token.balanceOf('0xAccount')
15+
await token.transfer('0xRecipient', 100n)
16+
```
17+
18+
## ERC-721
19+
20+
```typescript
21+
import {ERC721} from '@hgraph.io/sdk'
22+
import {JsonRpcProvider} from 'ethers'
23+
24+
const provider = new JsonRpcProvider('https://your.rpc.url')
25+
const nft = new ERC721('0xCollectionAddress', provider)
26+
27+
const owner = await nft.ownerOf(1n)
28+
const uri = await nft.tokenURI(1n)
29+
await nft.safeTransferFrom('0xOwner', '0xReceiver', 1n)
30+
```

src/tokens/erc20.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {Contract, type ContractRunner, type InterfaceAbi, type BigNumberish, type TransactionResponse} from 'ethers'
2+
3+
export const ERC20_ABI: InterfaceAbi = [
4+
'function name() view returns (string)',
5+
'function symbol() view returns (string)',
6+
'function decimals() view returns (uint8)',
7+
'function balanceOf(address account) view returns (uint256)',
8+
'function transfer(address to, uint256 amount) returns (bool)',
9+
'function transferFrom(address from, address to, uint256 amount) returns (bool)',
10+
]
11+
12+
export default class ERC20 {
13+
public contract: Contract
14+
15+
constructor(address: string, runner: ContractRunner) {
16+
this.contract = new Contract(address, ERC20_ABI, runner)
17+
}
18+
19+
name(): Promise<string> {
20+
return this.contract.name()
21+
}
22+
23+
symbol(): Promise<string> {
24+
return this.contract.symbol()
25+
}
26+
27+
decimals(): Promise<number> {
28+
return this.contract.decimals()
29+
}
30+
31+
balanceOf(account: string): Promise<bigint> {
32+
return this.contract.balanceOf(account)
33+
}
34+
35+
transfer(to: string, amount: BigNumberish): Promise<TransactionResponse> {
36+
return this.contract.transfer(to, amount)
37+
}
38+
39+
transferFrom(from: string, to: string, amount: BigNumberish): Promise<TransactionResponse> {
40+
return this.contract.transferFrom(from, to, amount)
41+
}
42+
}

src/tokens/erc721.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {Contract, type ContractRunner, type InterfaceAbi, type BigNumberish, type TransactionResponse} from 'ethers'
2+
3+
export const ERC721_ABI: InterfaceAbi = [
4+
'function name() view returns (string)',
5+
'function symbol() view returns (string)',
6+
'function ownerOf(uint256 tokenId) view returns (address)',
7+
'function tokenURI(uint256 tokenId) view returns (string)',
8+
'function transferFrom(address from, address to, uint256 tokenId)',
9+
'function safeTransferFrom(address from, address to, uint256 tokenId)',
10+
]
11+
12+
export default class ERC721 {
13+
public contract: Contract
14+
15+
constructor(address: string, runner: ContractRunner) {
16+
this.contract = new Contract(address, ERC721_ABI, runner)
17+
}
18+
19+
name(): Promise<string> {
20+
return this.contract.name()
21+
}
22+
23+
symbol(): Promise<string> {
24+
return this.contract.symbol()
25+
}
26+
27+
ownerOf(tokenId: BigNumberish): Promise<string> {
28+
return this.contract.ownerOf(tokenId)
29+
}
30+
31+
tokenURI(tokenId: BigNumberish): Promise<string> {
32+
return this.contract.tokenURI(tokenId)
33+
}
34+
35+
transferFrom(from: string, to: string, tokenId: BigNumberish): Promise<TransactionResponse> {
36+
return this.contract.transferFrom(from, to, tokenId)
37+
}
38+
39+
safeTransferFrom(from: string, to: string, tokenId: BigNumberish): Promise<TransactionResponse> {
40+
return this.contract.safeTransferFrom(from, to, tokenId)
41+
}
42+
}

src/tokens/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export {default as ERC20, ERC20_ABI} from './erc20'
2+
export {default as ERC721, ERC721_ABI} from './erc721'

src/types/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import {
44
Contract as EthersContract,
55
InterfaceAbi,
66
LogDescription as EventLogDescription,
7+
type ContractRunner,
8+
type BigNumberish,
9+
type TransactionResponse,
710
} from 'ethers'
811
import {DocumentNode} from 'graphql/language/ast'
912
import {Client as SubscriptionClient} from '../../graphql-ws/src'
@@ -154,6 +157,50 @@ export declare class HgraphContract {
154157
) => Promise<EventLogDescription[]>
155158
}
156159

160+
/*
161+
* ERC-20
162+
*/
163+
export declare const ERC20_ABI: InterfaceAbi
164+
165+
export declare class ERC20 {
166+
public contract: EthersContract
167+
constructor(address: string, runner: ContractRunner)
168+
name(): Promise<string>
169+
symbol(): Promise<string>
170+
decimals(): Promise<number>
171+
balanceOf(account: string): Promise<bigint>
172+
transfer(to: string, amount: BigNumberish): Promise<TransactionResponse>
173+
transferFrom(
174+
from: string,
175+
to: string,
176+
amount: BigNumberish
177+
): Promise<TransactionResponse>
178+
}
179+
180+
/*
181+
* ERC-721
182+
*/
183+
export declare const ERC721_ABI: InterfaceAbi
184+
185+
export declare class ERC721 {
186+
public contract: EthersContract
187+
constructor(address: string, runner: ContractRunner)
188+
name(): Promise<string>
189+
symbol(): Promise<string>
190+
ownerOf(tokenId: BigNumberish): Promise<string>
191+
tokenURI(tokenId: BigNumberish): Promise<string>
192+
transferFrom(
193+
from: string,
194+
to: string,
195+
tokenId: BigNumberish
196+
): Promise<TransactionResponse>
197+
safeTransferFrom(
198+
from: string,
199+
to: string,
200+
tokenId: BigNumberish
201+
): Promise<TransactionResponse>
202+
}
203+
157204
/*
158205
* Requests
159206
*/

tests/ERC20.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {describe, it, expect, vi} from 'vitest'
2+
import ERC20 from '../src/tokens/erc20'
3+
4+
describe('ERC20 wrapper', () => {
5+
it('calls underlying contract methods', async () => {
6+
const token = new ERC20('0x0', {} as any)
7+
const mock = {
8+
name: vi.fn().mockResolvedValue('Token'),
9+
symbol: vi.fn().mockResolvedValue('TKN'),
10+
decimals: vi.fn().mockResolvedValue(18),
11+
balanceOf: vi.fn().mockResolvedValue(5n),
12+
transfer: vi.fn().mockResolvedValue('tx1'),
13+
transferFrom: vi.fn().mockResolvedValue('tx2'),
14+
}
15+
;(token as any).contract = mock
16+
17+
expect(await token.name()).toBe('Token')
18+
expect(await token.symbol()).toBe('TKN')
19+
expect(await token.decimals()).toBe(18)
20+
expect(await token.balanceOf('0xabc')).toBe(5n)
21+
await token.transfer('0xdef', 1n)
22+
await token.transferFrom('0xabc', '0xdef', 2n)
23+
24+
expect(mock.name).toHaveBeenCalled()
25+
expect(mock.symbol).toHaveBeenCalled()
26+
expect(mock.decimals).toHaveBeenCalled()
27+
expect(mock.balanceOf).toHaveBeenCalledWith('0xabc')
28+
expect(mock.transfer).toHaveBeenCalledWith('0xdef', 1n)
29+
expect(mock.transferFrom).toHaveBeenCalledWith('0xabc', '0xdef', 2n)
30+
})
31+
})

tests/ERC721.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {describe, it, expect, vi} from 'vitest'
2+
import ERC721 from '../src/tokens/erc721'
3+
4+
describe('ERC721 wrapper', () => {
5+
it('calls underlying contract methods', async () => {
6+
const nft = new ERC721('0x0', {} as any)
7+
const mock = {
8+
name: vi.fn().mockResolvedValue('NFT'),
9+
symbol: vi.fn().mockResolvedValue('NFT'),
10+
ownerOf: vi.fn().mockResolvedValue('0xabc'),
11+
tokenURI: vi.fn().mockResolvedValue('uri'),
12+
transferFrom: vi.fn().mockResolvedValue('tx1'),
13+
safeTransferFrom: vi.fn().mockResolvedValue('tx2'),
14+
}
15+
;(nft as any).contract = mock
16+
17+
expect(await nft.name()).toBe('NFT')
18+
expect(await nft.symbol()).toBe('NFT')
19+
expect(await nft.ownerOf(1n)).toBe('0xabc')
20+
expect(await nft.tokenURI(1n)).toBe('uri')
21+
await nft.transferFrom('0xabc', '0xdef', 1n)
22+
await nft.safeTransferFrom('0xabc', '0xdef', 2n)
23+
24+
expect(mock.name).toHaveBeenCalled()
25+
expect(mock.symbol).toHaveBeenCalled()
26+
expect(mock.ownerOf).toHaveBeenCalledWith(1n)
27+
expect(mock.tokenURI).toHaveBeenCalledWith(1n)
28+
expect(mock.transferFrom).toHaveBeenCalledWith('0xabc', '0xdef', 1n)
29+
expect(mock.safeTransferFrom).toHaveBeenCalledWith('0xabc', '0xdef', 2n)
30+
})
31+
})

0 commit comments

Comments
 (0)