@octaflowlabs/onchain-sdk 1.1.3 → 1.2.0
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.
- package/README.md +53 -1
- package/dist/ABIs/MULTICALL3_ABI.d.ts +65 -0
- package/dist/ABIs/MULTICALL3_ABI.js +52 -0
- package/dist/blockchain/broadcastTransaction.js +3 -0
- package/dist/blockchain/buildUnsignedTransferTx.js +3 -0
- package/dist/blockchain/estimateGasLimitFromProvider.js +6 -0
- package/dist/blockchain/getBalances.d.ts +3 -0
- package/dist/blockchain/getBalances.js +121 -0
- package/dist/cjs/ABIs/MULTICALL3_ABI.d.ts +65 -0
- package/dist/cjs/ABIs/MULTICALL3_ABI.js +54 -0
- package/dist/cjs/blockchain/broadcastTransaction.js +3 -0
- package/dist/cjs/blockchain/buildUnsignedTransferTx.js +3 -0
- package/dist/cjs/blockchain/estimateGasLimitFromProvider.js +6 -0
- package/dist/cjs/blockchain/getBalances.d.ts +3 -0
- package/dist/cjs/blockchain/getBalances.js +129 -0
- package/dist/cjs/constants/constants.d.ts +1 -0
- package/dist/cjs/constants/constants.js +4 -1
- package/dist/cjs/index.d.ts +6 -2
- package/dist/cjs/index.js +12 -1
- package/dist/cjs/rpc/index.d.ts +3 -0
- package/dist/cjs/rpc/index.js +69 -0
- package/dist/cjs/types/common.d.ts +35 -0
- package/dist/cjs/utils/formatAmount.js +1 -2
- package/dist/cjs/utils/normalizeAddress.d.ts +1 -0
- package/dist/cjs/utils/normalizeAddress.js +15 -0
- package/dist/constants/constants.d.ts +1 -0
- package/dist/constants/constants.js +3 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/rpc/index.d.ts +3 -0
- package/dist/rpc/index.js +60 -0
- package/dist/types/common.d.ts +35 -0
- package/dist/utils/formatAmount.js +1 -2
- package/dist/utils/normalizeAddress.d.ts +1 -0
- package/dist/utils/normalizeAddress.js +11 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1 +1,53 @@
|
|
|
1
|
-
# onchain-sdk
|
|
1
|
+
# onchain-sdk
|
|
2
|
+
|
|
3
|
+
Lightweight TypeScript SDK for EVM onchain utilities. It provides helpers for
|
|
4
|
+
balances, transaction building, broadcasting, gas estimation, and wallet
|
|
5
|
+
derivation.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Install with npm or yarn.
|
|
10
|
+
|
|
11
|
+
## How to use
|
|
12
|
+
|
|
13
|
+
- Add the package to the project and import the helpers needed.
|
|
14
|
+
- Provide a valid RPC URL (public or private) and the target chain ID.
|
|
15
|
+
- Call the balance, transaction, or signing helpers based on your flow.
|
|
16
|
+
- Handle errors at the call site and decide how often to poll or refresh.
|
|
17
|
+
|
|
18
|
+
## What this SDK provides
|
|
19
|
+
|
|
20
|
+
Balances
|
|
21
|
+
|
|
22
|
+
- Fetch native token balances for an address.
|
|
23
|
+
- Fetch ERC-20 token balances for an address.
|
|
24
|
+
- Fetch multiple token balances across multiple chains with multicall batching.
|
|
25
|
+
|
|
26
|
+
Transactions
|
|
27
|
+
|
|
28
|
+
- Build unsigned native or ERC-20 transfer transactions.
|
|
29
|
+
- Estimate gas limits and fee data from a provider.
|
|
30
|
+
- Broadcast signed transactions.
|
|
31
|
+
- Check transaction status and receipt confirmation.
|
|
32
|
+
|
|
33
|
+
Wallets and signing
|
|
34
|
+
|
|
35
|
+
- Generate and derive EVM wallets from entropy.
|
|
36
|
+
- Sign messages and transactions.
|
|
37
|
+
|
|
38
|
+
Utilities
|
|
39
|
+
|
|
40
|
+
- Format and parse amounts for display.
|
|
41
|
+
- Normalize addresses and shorten hashes.
|
|
42
|
+
- Transform bigint values for UI use.
|
|
43
|
+
|
|
44
|
+
ABIs and constants
|
|
45
|
+
|
|
46
|
+
- ERC-20 ABI for balance and transfer calls.
|
|
47
|
+
- Multicall3 ABI and address for batched reads.
|
|
48
|
+
- Gas limit defaults per transaction type.
|
|
49
|
+
|
|
50
|
+
## Design notes
|
|
51
|
+
|
|
52
|
+
- The SDK is stateless and transport-agnostic. It expects a caller-provided RPC URL.
|
|
53
|
+
- Caching, polling, and background jobs should be handled by the consumer app or backend.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare const _default: readonly [{
|
|
2
|
+
readonly inputs: readonly [{
|
|
3
|
+
readonly components: readonly [{
|
|
4
|
+
readonly internalType: "address";
|
|
5
|
+
readonly name: "target";
|
|
6
|
+
readonly type: "address";
|
|
7
|
+
}, {
|
|
8
|
+
readonly internalType: "bytes";
|
|
9
|
+
readonly name: "callData";
|
|
10
|
+
readonly type: "bytes";
|
|
11
|
+
}];
|
|
12
|
+
readonly internalType: "struct Multicall3.Call[]";
|
|
13
|
+
readonly name: "calls";
|
|
14
|
+
readonly type: "tuple[]";
|
|
15
|
+
}];
|
|
16
|
+
readonly name: "aggregate";
|
|
17
|
+
readonly outputs: readonly [{
|
|
18
|
+
readonly internalType: "uint256";
|
|
19
|
+
readonly name: "blockNumber";
|
|
20
|
+
readonly type: "uint256";
|
|
21
|
+
}, {
|
|
22
|
+
readonly internalType: "bytes[]";
|
|
23
|
+
readonly name: "returnData";
|
|
24
|
+
readonly type: "bytes[]";
|
|
25
|
+
}];
|
|
26
|
+
readonly stateMutability: "payable";
|
|
27
|
+
readonly type: "function";
|
|
28
|
+
}, {
|
|
29
|
+
readonly inputs: readonly [{
|
|
30
|
+
readonly components: readonly [{
|
|
31
|
+
readonly internalType: "address";
|
|
32
|
+
readonly name: "target";
|
|
33
|
+
readonly type: "address";
|
|
34
|
+
}, {
|
|
35
|
+
readonly internalType: "bool";
|
|
36
|
+
readonly name: "allowFailure";
|
|
37
|
+
readonly type: "bool";
|
|
38
|
+
}, {
|
|
39
|
+
readonly internalType: "bytes";
|
|
40
|
+
readonly name: "callData";
|
|
41
|
+
readonly type: "bytes";
|
|
42
|
+
}];
|
|
43
|
+
readonly internalType: "struct Multicall3.Call3[]";
|
|
44
|
+
readonly name: "calls";
|
|
45
|
+
readonly type: "tuple[]";
|
|
46
|
+
}];
|
|
47
|
+
readonly name: "aggregate3";
|
|
48
|
+
readonly outputs: readonly [{
|
|
49
|
+
readonly components: readonly [{
|
|
50
|
+
readonly internalType: "bool";
|
|
51
|
+
readonly name: "success";
|
|
52
|
+
readonly type: "bool";
|
|
53
|
+
}, {
|
|
54
|
+
readonly internalType: "bytes";
|
|
55
|
+
readonly name: "returnData";
|
|
56
|
+
readonly type: "bytes";
|
|
57
|
+
}];
|
|
58
|
+
readonly internalType: "struct Multicall3.Result[]";
|
|
59
|
+
readonly name: "returnData";
|
|
60
|
+
readonly type: "tuple[]";
|
|
61
|
+
}];
|
|
62
|
+
readonly stateMutability: "payable";
|
|
63
|
+
readonly type: "function";
|
|
64
|
+
}];
|
|
65
|
+
export default _default;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// & Multicall3 ABI - standard contract deployed on most EVM chains
|
|
2
|
+
// & https://www.multicall3.com/
|
|
3
|
+
export default [
|
|
4
|
+
{
|
|
5
|
+
inputs: [
|
|
6
|
+
{
|
|
7
|
+
components: [
|
|
8
|
+
{ internalType: 'address', name: 'target', type: 'address' },
|
|
9
|
+
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
|
|
10
|
+
],
|
|
11
|
+
internalType: 'struct Multicall3.Call[]',
|
|
12
|
+
name: 'calls',
|
|
13
|
+
type: 'tuple[]',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
name: 'aggregate',
|
|
17
|
+
outputs: [
|
|
18
|
+
{ internalType: 'uint256', name: 'blockNumber', type: 'uint256' },
|
|
19
|
+
{ internalType: 'bytes[]', name: 'returnData', type: 'bytes[]' },
|
|
20
|
+
],
|
|
21
|
+
stateMutability: 'payable',
|
|
22
|
+
type: 'function',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
inputs: [
|
|
26
|
+
{
|
|
27
|
+
components: [
|
|
28
|
+
{ internalType: 'address', name: 'target', type: 'address' },
|
|
29
|
+
{ internalType: 'bool', name: 'allowFailure', type: 'bool' },
|
|
30
|
+
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
|
|
31
|
+
],
|
|
32
|
+
internalType: 'struct Multicall3.Call3[]',
|
|
33
|
+
name: 'calls',
|
|
34
|
+
type: 'tuple[]',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
name: 'aggregate3',
|
|
38
|
+
outputs: [
|
|
39
|
+
{
|
|
40
|
+
components: [
|
|
41
|
+
{ internalType: 'bool', name: 'success', type: 'bool' },
|
|
42
|
+
{ internalType: 'bytes', name: 'returnData', type: 'bytes' },
|
|
43
|
+
],
|
|
44
|
+
internalType: 'struct Multicall3.Result[]',
|
|
45
|
+
name: 'returnData',
|
|
46
|
+
type: 'tuple[]',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
stateMutability: 'payable',
|
|
50
|
+
type: 'function',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
@@ -3,7 +3,10 @@ import { Transaction } from 'ethers';
|
|
|
3
3
|
/** local imports */
|
|
4
4
|
import { getProvider } from './getProvider';
|
|
5
5
|
import { errorMessagesForBroadcast, handleErrorMessages } from '../utils/handleErrorMessages';
|
|
6
|
+
import { ensurePublicHost, testJsonRpc } from '../rpc';
|
|
6
7
|
export const broadcastTransaction = async ({ signedTx, rpcUrl, chainId, waitConfirmations = 0, }) => {
|
|
8
|
+
await ensurePublicHost(rpcUrl);
|
|
9
|
+
await testJsonRpc(rpcUrl, 'eth_chainId', []);
|
|
7
10
|
const provider = getProvider(rpcUrl, chainId);
|
|
8
11
|
if (!provider)
|
|
9
12
|
throw new Error('Could not create provider with given rpcUrl');
|
|
@@ -3,7 +3,10 @@ import { Interface, formatUnits, parseUnits } from 'ethers';
|
|
|
3
3
|
/** local imports */
|
|
4
4
|
import { getProvider } from './getProvider';
|
|
5
5
|
import { estimateGasLimitFromProvider } from './estimateGasLimitFromProvider';
|
|
6
|
+
import { ensurePublicHost, testJsonRpc } from '../rpc';
|
|
6
7
|
export const buildUnsignedTransferTx = async (options) => {
|
|
8
|
+
await ensurePublicHost(options.rpcUrl);
|
|
9
|
+
await testJsonRpc(options.rpcUrl, 'eth_chainId', []);
|
|
7
10
|
const provider = getProvider(options.rpcUrl, options.chainId);
|
|
8
11
|
if (!provider)
|
|
9
12
|
throw new Error('Could not create provider with given rpcUrl and chainId');
|
|
@@ -29,6 +29,12 @@ export const estimateGasLimitFromProvider = async ({ provider, unsignedTx, walle
|
|
|
29
29
|
}
|
|
30
30
|
const bufferPercentage = Math.min(Math.max(Math.round(congestionFactor * 5), 5), 30); // 5% to 30% buffer
|
|
31
31
|
const newGasLimit = gasEstimated + (gasEstimated * BigInt(bufferPercentage)) / BigInt(100);
|
|
32
|
+
// let suggested: GasFeesApiResponse | undefined = undefined
|
|
33
|
+
// try {
|
|
34
|
+
// suggested = await this.getSuggestedGasPrice((await provider.getNetwork()).chainId)
|
|
35
|
+
// } catch {
|
|
36
|
+
// suggested = undefined
|
|
37
|
+
// }
|
|
32
38
|
return {
|
|
33
39
|
gasEstimated,
|
|
34
40
|
gasLimit: newGasLimit,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { GetBalanceParams, GetBalanceResult, GetBalancesParams } from '../types/common';
|
|
2
|
+
export declare const getBalance: ({ walletAddress, rpcUrl, tokenAddress, chainId }: GetBalanceParams) => Promise<any> | undefined;
|
|
3
|
+
export declare const getBalances: ({ walletAddress, chains: chainRequests, }: GetBalancesParams) => Promise<GetBalanceResult>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** npm imports */
|
|
2
|
+
import { Contract, Interface } from 'ethers';
|
|
3
|
+
/** local imports */
|
|
4
|
+
import { getProvider } from './getProvider';
|
|
5
|
+
import ERC20_TOKEN_CONTRACT_ABI from '../ABIs/ERC20_TOKEN_CONTRACT_ABI';
|
|
6
|
+
import MULTICALL3_ABI from '../ABIs/MULTICALL3_ABI';
|
|
7
|
+
import { MULTICALL3_ADDRESS } from '../constants/constants';
|
|
8
|
+
import { handleErrorMessages } from '../utils/handleErrorMessages';
|
|
9
|
+
export const getBalance = ({ walletAddress, rpcUrl, tokenAddress, chainId }) => {
|
|
10
|
+
const provider = getProvider(rpcUrl, chainId);
|
|
11
|
+
if (!provider)
|
|
12
|
+
throw new Error('Failed to create provider with the given RPC URL and chain ID.');
|
|
13
|
+
try {
|
|
14
|
+
if (!tokenAddress)
|
|
15
|
+
return provider.getBalance(walletAddress);
|
|
16
|
+
const tokenContract = new Contract(tokenAddress, ERC20_TOKEN_CONTRACT_ABI, provider);
|
|
17
|
+
return tokenContract.balanceOf(walletAddress);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.error('Error fetching balance:', error);
|
|
21
|
+
handleErrorMessages({ e: error, message: 'Error fetching balance' });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const buildChainKey = (rpcUrl, chainId) => `${rpcUrl}|${chainId ?? 'default'}`;
|
|
25
|
+
const buildRequestKey = ({ walletAddress, rpcUrl, chainId, tokenAddress, }) => {
|
|
26
|
+
const walletKey = walletAddress.toLowerCase();
|
|
27
|
+
const tokenKey = tokenAddress ? tokenAddress.toLowerCase() : 'native';
|
|
28
|
+
const chainKey = chainId ?? 'default';
|
|
29
|
+
return `${walletKey}|${rpcUrl}|${chainKey}|${tokenKey}`;
|
|
30
|
+
};
|
|
31
|
+
const fetchTokenBalancesWithMulticall = async (walletAddress, tokenAddresses, rpcUrl, chainId) => {
|
|
32
|
+
const provider = getProvider(rpcUrl, chainId);
|
|
33
|
+
if (!provider)
|
|
34
|
+
return tokenAddresses.map(() => null);
|
|
35
|
+
try {
|
|
36
|
+
const erc20Interface = new Interface(ERC20_TOKEN_CONTRACT_ABI);
|
|
37
|
+
const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
38
|
+
const calls = tokenAddresses.map((tokenAddress) => ({
|
|
39
|
+
target: tokenAddress,
|
|
40
|
+
allowFailure: true,
|
|
41
|
+
callData: erc20Interface.encodeFunctionData('balanceOf', [walletAddress]),
|
|
42
|
+
}));
|
|
43
|
+
const results = await multicall.aggregate3.staticCall(calls);
|
|
44
|
+
return results.map((result, index) => {
|
|
45
|
+
if (!result.success || result.returnData === '0x')
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const decoded = erc20Interface.decodeFunctionResult('balanceOf', result.returnData);
|
|
49
|
+
return decoded[0];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
console.error(`Failed to decode balance for token ${tokenAddresses[index]}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('Multicall failed, falling back to individual calls:', error);
|
|
59
|
+
return Promise.allSettled(tokenAddresses.map((tokenAddress) => getBalance({ walletAddress, rpcUrl, tokenAddress, chainId }))).then((results) => results.map((r) => (r.status === 'fulfilled' && r.value ? r.value : null)));
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export const getBalances = async ({ walletAddress, chains: chainRequests, }) => {
|
|
63
|
+
const chainGroups = new Map();
|
|
64
|
+
chainRequests.forEach(({ rpcUrl, chainId, tokenAddresses, includeNative }) => {
|
|
65
|
+
const chainKey = buildChainKey(rpcUrl, chainId);
|
|
66
|
+
if (!chainGroups.has(chainKey)) {
|
|
67
|
+
chainGroups.set(chainKey, {
|
|
68
|
+
rpcUrl,
|
|
69
|
+
chainId,
|
|
70
|
+
nativeBalanceRequests: [],
|
|
71
|
+
tokenBalanceRequests: [],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const group = chainGroups.get(chainKey);
|
|
75
|
+
if (includeNative) {
|
|
76
|
+
const nativeRequest = { walletAddress, rpcUrl, chainId };
|
|
77
|
+
const key = buildRequestKey(nativeRequest);
|
|
78
|
+
if (!group.nativeBalanceRequests.some((r) => buildRequestKey(r) === key))
|
|
79
|
+
group.nativeBalanceRequests.push(nativeRequest);
|
|
80
|
+
}
|
|
81
|
+
tokenAddresses.forEach((tokenAddress) => {
|
|
82
|
+
const request = { walletAddress, rpcUrl, chainId, tokenAddress };
|
|
83
|
+
const key = buildRequestKey(request);
|
|
84
|
+
if (!group.tokenBalanceRequests.some((r) => buildRequestKey(r) === key))
|
|
85
|
+
group.tokenBalanceRequests.push(request);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
const chainResults = await Promise.allSettled(Array.from(chainGroups.values()).map(async (group) => {
|
|
89
|
+
const tokenBalances = [];
|
|
90
|
+
const nativeResults = await Promise.allSettled(group.nativeBalanceRequests.map((request) => getBalance(request)));
|
|
91
|
+
nativeResults.forEach((result) => {
|
|
92
|
+
tokenBalances.push({
|
|
93
|
+
tokenAddress: null,
|
|
94
|
+
balance: result.status === 'fulfilled' ? (result.value ?? null) : null,
|
|
95
|
+
error: result.status === 'rejected' ? result.reason : undefined,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
if (group.tokenBalanceRequests.length > 0) {
|
|
99
|
+
const tokenAddresses = group.tokenBalanceRequests.map((r) => r.tokenAddress);
|
|
100
|
+
const balances = await fetchTokenBalancesWithMulticall(walletAddress, tokenAddresses, group.rpcUrl, group.chainId);
|
|
101
|
+
balances.forEach((balance, index) => {
|
|
102
|
+
tokenBalances.push({
|
|
103
|
+
tokenAddress: group.tokenBalanceRequests[index].tokenAddress ?? null,
|
|
104
|
+
balance,
|
|
105
|
+
error: balance === null ? 'Failed to fetch balance' : undefined,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
chainId: group.chainId,
|
|
111
|
+
balances: tokenBalances,
|
|
112
|
+
};
|
|
113
|
+
}));
|
|
114
|
+
const chainsWithBalances = chainResults
|
|
115
|
+
.filter((result) => result.status === 'fulfilled')
|
|
116
|
+
.map((result) => result.value);
|
|
117
|
+
return {
|
|
118
|
+
walletAddress,
|
|
119
|
+
chains: chainsWithBalances,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare const _default: readonly [{
|
|
2
|
+
readonly inputs: readonly [{
|
|
3
|
+
readonly components: readonly [{
|
|
4
|
+
readonly internalType: "address";
|
|
5
|
+
readonly name: "target";
|
|
6
|
+
readonly type: "address";
|
|
7
|
+
}, {
|
|
8
|
+
readonly internalType: "bytes";
|
|
9
|
+
readonly name: "callData";
|
|
10
|
+
readonly type: "bytes";
|
|
11
|
+
}];
|
|
12
|
+
readonly internalType: "struct Multicall3.Call[]";
|
|
13
|
+
readonly name: "calls";
|
|
14
|
+
readonly type: "tuple[]";
|
|
15
|
+
}];
|
|
16
|
+
readonly name: "aggregate";
|
|
17
|
+
readonly outputs: readonly [{
|
|
18
|
+
readonly internalType: "uint256";
|
|
19
|
+
readonly name: "blockNumber";
|
|
20
|
+
readonly type: "uint256";
|
|
21
|
+
}, {
|
|
22
|
+
readonly internalType: "bytes[]";
|
|
23
|
+
readonly name: "returnData";
|
|
24
|
+
readonly type: "bytes[]";
|
|
25
|
+
}];
|
|
26
|
+
readonly stateMutability: "payable";
|
|
27
|
+
readonly type: "function";
|
|
28
|
+
}, {
|
|
29
|
+
readonly inputs: readonly [{
|
|
30
|
+
readonly components: readonly [{
|
|
31
|
+
readonly internalType: "address";
|
|
32
|
+
readonly name: "target";
|
|
33
|
+
readonly type: "address";
|
|
34
|
+
}, {
|
|
35
|
+
readonly internalType: "bool";
|
|
36
|
+
readonly name: "allowFailure";
|
|
37
|
+
readonly type: "bool";
|
|
38
|
+
}, {
|
|
39
|
+
readonly internalType: "bytes";
|
|
40
|
+
readonly name: "callData";
|
|
41
|
+
readonly type: "bytes";
|
|
42
|
+
}];
|
|
43
|
+
readonly internalType: "struct Multicall3.Call3[]";
|
|
44
|
+
readonly name: "calls";
|
|
45
|
+
readonly type: "tuple[]";
|
|
46
|
+
}];
|
|
47
|
+
readonly name: "aggregate3";
|
|
48
|
+
readonly outputs: readonly [{
|
|
49
|
+
readonly components: readonly [{
|
|
50
|
+
readonly internalType: "bool";
|
|
51
|
+
readonly name: "success";
|
|
52
|
+
readonly type: "bool";
|
|
53
|
+
}, {
|
|
54
|
+
readonly internalType: "bytes";
|
|
55
|
+
readonly name: "returnData";
|
|
56
|
+
readonly type: "bytes";
|
|
57
|
+
}];
|
|
58
|
+
readonly internalType: "struct Multicall3.Result[]";
|
|
59
|
+
readonly name: "returnData";
|
|
60
|
+
readonly type: "tuple[]";
|
|
61
|
+
}];
|
|
62
|
+
readonly stateMutability: "payable";
|
|
63
|
+
readonly type: "function";
|
|
64
|
+
}];
|
|
65
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// & Multicall3 ABI - standard contract deployed on most EVM chains
|
|
4
|
+
// & https://www.multicall3.com/
|
|
5
|
+
exports.default = [
|
|
6
|
+
{
|
|
7
|
+
inputs: [
|
|
8
|
+
{
|
|
9
|
+
components: [
|
|
10
|
+
{ internalType: 'address', name: 'target', type: 'address' },
|
|
11
|
+
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
|
|
12
|
+
],
|
|
13
|
+
internalType: 'struct Multicall3.Call[]',
|
|
14
|
+
name: 'calls',
|
|
15
|
+
type: 'tuple[]',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
name: 'aggregate',
|
|
19
|
+
outputs: [
|
|
20
|
+
{ internalType: 'uint256', name: 'blockNumber', type: 'uint256' },
|
|
21
|
+
{ internalType: 'bytes[]', name: 'returnData', type: 'bytes[]' },
|
|
22
|
+
],
|
|
23
|
+
stateMutability: 'payable',
|
|
24
|
+
type: 'function',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
inputs: [
|
|
28
|
+
{
|
|
29
|
+
components: [
|
|
30
|
+
{ internalType: 'address', name: 'target', type: 'address' },
|
|
31
|
+
{ internalType: 'bool', name: 'allowFailure', type: 'bool' },
|
|
32
|
+
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
|
|
33
|
+
],
|
|
34
|
+
internalType: 'struct Multicall3.Call3[]',
|
|
35
|
+
name: 'calls',
|
|
36
|
+
type: 'tuple[]',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
name: 'aggregate3',
|
|
40
|
+
outputs: [
|
|
41
|
+
{
|
|
42
|
+
components: [
|
|
43
|
+
{ internalType: 'bool', name: 'success', type: 'bool' },
|
|
44
|
+
{ internalType: 'bytes', name: 'returnData', type: 'bytes' },
|
|
45
|
+
],
|
|
46
|
+
internalType: 'struct Multicall3.Result[]',
|
|
47
|
+
name: 'returnData',
|
|
48
|
+
type: 'tuple[]',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
stateMutability: 'payable',
|
|
52
|
+
type: 'function',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
@@ -6,7 +6,10 @@ const ethers_1 = require("ethers");
|
|
|
6
6
|
/** local imports */
|
|
7
7
|
const getProvider_1 = require("./getProvider");
|
|
8
8
|
const handleErrorMessages_1 = require("../utils/handleErrorMessages");
|
|
9
|
+
const rpc_1 = require("../rpc");
|
|
9
10
|
const broadcastTransaction = async ({ signedTx, rpcUrl, chainId, waitConfirmations = 0, }) => {
|
|
11
|
+
await (0, rpc_1.ensurePublicHost)(rpcUrl);
|
|
12
|
+
await (0, rpc_1.testJsonRpc)(rpcUrl, 'eth_chainId', []);
|
|
10
13
|
const provider = (0, getProvider_1.getProvider)(rpcUrl, chainId);
|
|
11
14
|
if (!provider)
|
|
12
15
|
throw new Error('Could not create provider with given rpcUrl');
|
|
@@ -6,7 +6,10 @@ const ethers_1 = require("ethers");
|
|
|
6
6
|
/** local imports */
|
|
7
7
|
const getProvider_1 = require("./getProvider");
|
|
8
8
|
const estimateGasLimitFromProvider_1 = require("./estimateGasLimitFromProvider");
|
|
9
|
+
const rpc_1 = require("../rpc");
|
|
9
10
|
const buildUnsignedTransferTx = async (options) => {
|
|
11
|
+
await (0, rpc_1.ensurePublicHost)(options.rpcUrl);
|
|
12
|
+
await (0, rpc_1.testJsonRpc)(options.rpcUrl, 'eth_chainId', []);
|
|
10
13
|
const provider = (0, getProvider_1.getProvider)(options.rpcUrl, options.chainId);
|
|
11
14
|
if (!provider)
|
|
12
15
|
throw new Error('Could not create provider with given rpcUrl and chainId');
|
|
@@ -32,6 +32,12 @@ const estimateGasLimitFromProvider = async ({ provider, unsignedTx, walletAddres
|
|
|
32
32
|
}
|
|
33
33
|
const bufferPercentage = Math.min(Math.max(Math.round(congestionFactor * 5), 5), 30); // 5% to 30% buffer
|
|
34
34
|
const newGasLimit = gasEstimated + (gasEstimated * BigInt(bufferPercentage)) / BigInt(100);
|
|
35
|
+
// let suggested: GasFeesApiResponse | undefined = undefined
|
|
36
|
+
// try {
|
|
37
|
+
// suggested = await this.getSuggestedGasPrice((await provider.getNetwork()).chainId)
|
|
38
|
+
// } catch {
|
|
39
|
+
// suggested = undefined
|
|
40
|
+
// }
|
|
35
41
|
return {
|
|
36
42
|
gasEstimated,
|
|
37
43
|
gasLimit: newGasLimit,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { GetBalanceParams, GetBalanceResult, GetBalancesParams } from '../types/common';
|
|
2
|
+
export declare const getBalance: ({ walletAddress, rpcUrl, tokenAddress, chainId }: GetBalanceParams) => Promise<any> | undefined;
|
|
3
|
+
export declare const getBalances: ({ walletAddress, chains: chainRequests, }: GetBalancesParams) => Promise<GetBalanceResult>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getBalances = exports.getBalance = void 0;
|
|
7
|
+
/** npm imports */
|
|
8
|
+
const ethers_1 = require("ethers");
|
|
9
|
+
/** local imports */
|
|
10
|
+
const getProvider_1 = require("./getProvider");
|
|
11
|
+
const ERC20_TOKEN_CONTRACT_ABI_1 = __importDefault(require("../ABIs/ERC20_TOKEN_CONTRACT_ABI"));
|
|
12
|
+
const MULTICALL3_ABI_1 = __importDefault(require("../ABIs/MULTICALL3_ABI"));
|
|
13
|
+
const constants_1 = require("../constants/constants");
|
|
14
|
+
const handleErrorMessages_1 = require("../utils/handleErrorMessages");
|
|
15
|
+
const getBalance = ({ walletAddress, rpcUrl, tokenAddress, chainId }) => {
|
|
16
|
+
const provider = (0, getProvider_1.getProvider)(rpcUrl, chainId);
|
|
17
|
+
if (!provider)
|
|
18
|
+
throw new Error('Failed to create provider with the given RPC URL and chain ID.');
|
|
19
|
+
try {
|
|
20
|
+
if (!tokenAddress)
|
|
21
|
+
return provider.getBalance(walletAddress);
|
|
22
|
+
const tokenContract = new ethers_1.Contract(tokenAddress, ERC20_TOKEN_CONTRACT_ABI_1.default, provider);
|
|
23
|
+
return tokenContract.balanceOf(walletAddress);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error('Error fetching balance:', error);
|
|
27
|
+
(0, handleErrorMessages_1.handleErrorMessages)({ e: error, message: 'Error fetching balance' });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.getBalance = getBalance;
|
|
31
|
+
const buildChainKey = (rpcUrl, chainId) => `${rpcUrl}|${chainId ?? 'default'}`;
|
|
32
|
+
const buildRequestKey = ({ walletAddress, rpcUrl, chainId, tokenAddress, }) => {
|
|
33
|
+
const walletKey = walletAddress.toLowerCase();
|
|
34
|
+
const tokenKey = tokenAddress ? tokenAddress.toLowerCase() : 'native';
|
|
35
|
+
const chainKey = chainId ?? 'default';
|
|
36
|
+
return `${walletKey}|${rpcUrl}|${chainKey}|${tokenKey}`;
|
|
37
|
+
};
|
|
38
|
+
const fetchTokenBalancesWithMulticall = async (walletAddress, tokenAddresses, rpcUrl, chainId) => {
|
|
39
|
+
const provider = (0, getProvider_1.getProvider)(rpcUrl, chainId);
|
|
40
|
+
if (!provider)
|
|
41
|
+
return tokenAddresses.map(() => null);
|
|
42
|
+
try {
|
|
43
|
+
const erc20Interface = new ethers_1.Interface(ERC20_TOKEN_CONTRACT_ABI_1.default);
|
|
44
|
+
const multicall = new ethers_1.Contract(constants_1.MULTICALL3_ADDRESS, MULTICALL3_ABI_1.default, provider);
|
|
45
|
+
const calls = tokenAddresses.map((tokenAddress) => ({
|
|
46
|
+
target: tokenAddress,
|
|
47
|
+
allowFailure: true,
|
|
48
|
+
callData: erc20Interface.encodeFunctionData('balanceOf', [walletAddress]),
|
|
49
|
+
}));
|
|
50
|
+
const results = await multicall.aggregate3.staticCall(calls);
|
|
51
|
+
return results.map((result, index) => {
|
|
52
|
+
if (!result.success || result.returnData === '0x')
|
|
53
|
+
return null;
|
|
54
|
+
try {
|
|
55
|
+
const decoded = erc20Interface.decodeFunctionResult('balanceOf', result.returnData);
|
|
56
|
+
return decoded[0];
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
console.error(`Failed to decode balance for token ${tokenAddresses[index]}`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error('Multicall failed, falling back to individual calls:', error);
|
|
66
|
+
return Promise.allSettled(tokenAddresses.map((tokenAddress) => (0, exports.getBalance)({ walletAddress, rpcUrl, tokenAddress, chainId }))).then((results) => results.map((r) => (r.status === 'fulfilled' && r.value ? r.value : null)));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const getBalances = async ({ walletAddress, chains: chainRequests, }) => {
|
|
70
|
+
const chainGroups = new Map();
|
|
71
|
+
chainRequests.forEach(({ rpcUrl, chainId, tokenAddresses, includeNative }) => {
|
|
72
|
+
const chainKey = buildChainKey(rpcUrl, chainId);
|
|
73
|
+
if (!chainGroups.has(chainKey)) {
|
|
74
|
+
chainGroups.set(chainKey, {
|
|
75
|
+
rpcUrl,
|
|
76
|
+
chainId,
|
|
77
|
+
nativeBalanceRequests: [],
|
|
78
|
+
tokenBalanceRequests: [],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const group = chainGroups.get(chainKey);
|
|
82
|
+
if (includeNative) {
|
|
83
|
+
const nativeRequest = { walletAddress, rpcUrl, chainId };
|
|
84
|
+
const key = buildRequestKey(nativeRequest);
|
|
85
|
+
if (!group.nativeBalanceRequests.some((r) => buildRequestKey(r) === key))
|
|
86
|
+
group.nativeBalanceRequests.push(nativeRequest);
|
|
87
|
+
}
|
|
88
|
+
tokenAddresses.forEach((tokenAddress) => {
|
|
89
|
+
const request = { walletAddress, rpcUrl, chainId, tokenAddress };
|
|
90
|
+
const key = buildRequestKey(request);
|
|
91
|
+
if (!group.tokenBalanceRequests.some((r) => buildRequestKey(r) === key))
|
|
92
|
+
group.tokenBalanceRequests.push(request);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
const chainResults = await Promise.allSettled(Array.from(chainGroups.values()).map(async (group) => {
|
|
96
|
+
const tokenBalances = [];
|
|
97
|
+
const nativeResults = await Promise.allSettled(group.nativeBalanceRequests.map((request) => (0, exports.getBalance)(request)));
|
|
98
|
+
nativeResults.forEach((result) => {
|
|
99
|
+
tokenBalances.push({
|
|
100
|
+
tokenAddress: null,
|
|
101
|
+
balance: result.status === 'fulfilled' ? (result.value ?? null) : null,
|
|
102
|
+
error: result.status === 'rejected' ? result.reason : undefined,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
if (group.tokenBalanceRequests.length > 0) {
|
|
106
|
+
const tokenAddresses = group.tokenBalanceRequests.map((r) => r.tokenAddress);
|
|
107
|
+
const balances = await fetchTokenBalancesWithMulticall(walletAddress, tokenAddresses, group.rpcUrl, group.chainId);
|
|
108
|
+
balances.forEach((balance, index) => {
|
|
109
|
+
tokenBalances.push({
|
|
110
|
+
tokenAddress: group.tokenBalanceRequests[index].tokenAddress ?? null,
|
|
111
|
+
balance,
|
|
112
|
+
error: balance === null ? 'Failed to fetch balance' : undefined,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
chainId: group.chainId,
|
|
118
|
+
balances: tokenBalances,
|
|
119
|
+
};
|
|
120
|
+
}));
|
|
121
|
+
const chainsWithBalances = chainResults
|
|
122
|
+
.filter((result) => result.status === 'fulfilled')
|
|
123
|
+
.map((result) => result.value);
|
|
124
|
+
return {
|
|
125
|
+
walletAddress,
|
|
126
|
+
chains: chainsWithBalances,
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
exports.getBalances = getBalances;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GAS_LIMIT_PER_TX_TYPE = void 0;
|
|
3
|
+
exports.MULTICALL3_ADDRESS = exports.GAS_LIMIT_PER_TX_TYPE = void 0;
|
|
4
4
|
exports.GAS_LIMIT_PER_TX_TYPE = {
|
|
5
5
|
DEFAULT_TRANSFER_NATIVE: 21000n,
|
|
6
6
|
DEFAULT_TRANSFER_ERC20: 65000n,
|
|
7
7
|
DEFAULT_APPROVAL: 100000n,
|
|
8
8
|
};
|
|
9
|
+
// & Multicall3 contract address (same on most chains)
|
|
10
|
+
// & https://www.multicall3.com/deployments
|
|
11
|
+
exports.MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import ERC20_TOKEN_CONTRACT_ABI from './ABIs/ERC20_TOKEN_CONTRACT_ABI';
|
|
3
3
|
export { ERC20_TOKEN_CONTRACT_ABI };
|
|
4
4
|
/** constants exports */
|
|
5
|
-
export { GAS_LIMIT_PER_TX_TYPE } from './constants/constants';
|
|
5
|
+
export { GAS_LIMIT_PER_TX_TYPE, MULTICALL3_ADDRESS } from './constants/constants';
|
|
6
6
|
/** basic blockchain exports */
|
|
7
7
|
export { buildMaxNativeTransferTx, buildUnsignedTransferTx, } from './blockchain/buildUnsignedTransferTx';
|
|
8
8
|
export { broadcastTransaction } from './blockchain/broadcastTransaction';
|
|
9
9
|
export { estimateGasLimitFromProvider } from './blockchain/estimateGasLimitFromProvider';
|
|
10
10
|
export { getProvider } from './blockchain/getProvider';
|
|
11
11
|
export { txStatus } from './blockchain/txStatus';
|
|
12
|
+
export { getBalance, getBalances } from './blockchain/getBalances';
|
|
12
13
|
/** services exports */
|
|
13
14
|
export { EvmWalletService, EvmGeneratedWallet, EvmDerivedWallet, } from './services/evm-wallet-core/evmWalletService';
|
|
14
15
|
export { EntropySource } from './services/evm-wallet-core/entropy';
|
|
@@ -20,5 +21,8 @@ import NATIVE_TOKENS from './utils/tokens';
|
|
|
20
21
|
export { NATIVE_TOKENS };
|
|
21
22
|
export { formattedAmountForDisplay, parsedAmount } from './utils/formatAmount';
|
|
22
23
|
export { handleErrorMessages, errorMessagesForBroadcast, errorMessagesForGasLimitEstimation, } from './utils/handleErrorMessages';
|
|
24
|
+
export { normalizeAddress } from './utils/normalizeAddress';
|
|
25
|
+
/** rpc exports */
|
|
26
|
+
export { validateRpcUrl, ensurePublicHost, testJsonRpc } from './rpc/index';
|
|
23
27
|
/** types exports */
|
|
24
|
-
export { BroadcastTransactionOptions, BuildMaxNativeTransferTxOptions, BuildMaxNativeTransferTxResponse, BuildUnsignedTransferTxOptions, EstimateGasLimitFromProviderProps, GasEstimateResult, TxStatusOptions, TxStatusResponse, UnsignedTransferTxResponse, FormatAmountOptions, TransactionRequest, } from './types/common';
|
|
28
|
+
export { BroadcastTransactionOptions, BuildMaxNativeTransferTxOptions, BuildMaxNativeTransferTxResponse, BuildUnsignedTransferTxOptions, EstimateGasLimitFromProviderProps, GasEstimateResult, TxStatusOptions, TxStatusResponse, UnsignedTransferTxResponse, FormatAmountOptions, TransactionRequest, GetBalanceParams, GetBalancesParams, GetBalancesChainRequest, GetBalanceResult, ChainBalances, TokenBalance, ChainGroup, } from './types/common';
|
package/dist/cjs/index.js
CHANGED
|
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.errorMessagesForGasLimitEstimation = exports.errorMessagesForBroadcast = exports.handleErrorMessages = exports.parsedAmount = exports.formattedAmountForDisplay = exports.NATIVE_TOKENS = exports.transformBigInt = exports.getShortenData = exports.getShortenTransactionHashOrAddress = exports.signTransaction = exports.signMessage = exports.createWallet = exports.EvmWalletService = exports.txStatus = exports.getProvider = exports.estimateGasLimitFromProvider = exports.broadcastTransaction = exports.buildUnsignedTransferTx = exports.buildMaxNativeTransferTx = exports.GAS_LIMIT_PER_TX_TYPE = exports.ERC20_TOKEN_CONTRACT_ABI = void 0;
|
|
6
|
+
exports.testJsonRpc = exports.ensurePublicHost = exports.validateRpcUrl = exports.normalizeAddress = exports.errorMessagesForGasLimitEstimation = exports.errorMessagesForBroadcast = exports.handleErrorMessages = exports.parsedAmount = exports.formattedAmountForDisplay = exports.NATIVE_TOKENS = exports.transformBigInt = exports.getShortenData = exports.getShortenTransactionHashOrAddress = exports.signTransaction = exports.signMessage = exports.createWallet = exports.EvmWalletService = exports.getBalances = exports.getBalance = exports.txStatus = exports.getProvider = exports.estimateGasLimitFromProvider = exports.broadcastTransaction = exports.buildUnsignedTransferTx = exports.buildMaxNativeTransferTx = exports.MULTICALL3_ADDRESS = exports.GAS_LIMIT_PER_TX_TYPE = exports.ERC20_TOKEN_CONTRACT_ABI = void 0;
|
|
7
7
|
/** ABIs exports */
|
|
8
8
|
const ERC20_TOKEN_CONTRACT_ABI_1 = __importDefault(require("./ABIs/ERC20_TOKEN_CONTRACT_ABI"));
|
|
9
9
|
exports.ERC20_TOKEN_CONTRACT_ABI = ERC20_TOKEN_CONTRACT_ABI_1.default;
|
|
10
10
|
/** constants exports */
|
|
11
11
|
var constants_1 = require("./constants/constants");
|
|
12
12
|
Object.defineProperty(exports, "GAS_LIMIT_PER_TX_TYPE", { enumerable: true, get: function () { return constants_1.GAS_LIMIT_PER_TX_TYPE; } });
|
|
13
|
+
Object.defineProperty(exports, "MULTICALL3_ADDRESS", { enumerable: true, get: function () { return constants_1.MULTICALL3_ADDRESS; } });
|
|
13
14
|
/** basic blockchain exports */
|
|
14
15
|
var buildUnsignedTransferTx_1 = require("./blockchain/buildUnsignedTransferTx");
|
|
15
16
|
Object.defineProperty(exports, "buildMaxNativeTransferTx", { enumerable: true, get: function () { return buildUnsignedTransferTx_1.buildMaxNativeTransferTx; } });
|
|
@@ -22,6 +23,9 @@ var getProvider_1 = require("./blockchain/getProvider");
|
|
|
22
23
|
Object.defineProperty(exports, "getProvider", { enumerable: true, get: function () { return getProvider_1.getProvider; } });
|
|
23
24
|
var txStatus_1 = require("./blockchain/txStatus");
|
|
24
25
|
Object.defineProperty(exports, "txStatus", { enumerable: true, get: function () { return txStatus_1.txStatus; } });
|
|
26
|
+
var getBalances_1 = require("./blockchain/getBalances");
|
|
27
|
+
Object.defineProperty(exports, "getBalance", { enumerable: true, get: function () { return getBalances_1.getBalance; } });
|
|
28
|
+
Object.defineProperty(exports, "getBalances", { enumerable: true, get: function () { return getBalances_1.getBalances; } });
|
|
25
29
|
/** services exports */
|
|
26
30
|
var evmWalletService_1 = require("./services/evm-wallet-core/evmWalletService");
|
|
27
31
|
Object.defineProperty(exports, "EvmWalletService", { enumerable: true, get: function () { return evmWalletService_1.EvmWalletService; } });
|
|
@@ -44,3 +48,10 @@ var handleErrorMessages_1 = require("./utils/handleErrorMessages");
|
|
|
44
48
|
Object.defineProperty(exports, "handleErrorMessages", { enumerable: true, get: function () { return handleErrorMessages_1.handleErrorMessages; } });
|
|
45
49
|
Object.defineProperty(exports, "errorMessagesForBroadcast", { enumerable: true, get: function () { return handleErrorMessages_1.errorMessagesForBroadcast; } });
|
|
46
50
|
Object.defineProperty(exports, "errorMessagesForGasLimitEstimation", { enumerable: true, get: function () { return handleErrorMessages_1.errorMessagesForGasLimitEstimation; } });
|
|
51
|
+
var normalizeAddress_1 = require("./utils/normalizeAddress");
|
|
52
|
+
Object.defineProperty(exports, "normalizeAddress", { enumerable: true, get: function () { return normalizeAddress_1.normalizeAddress; } });
|
|
53
|
+
/** rpc exports */
|
|
54
|
+
var index_1 = require("./rpc/index");
|
|
55
|
+
Object.defineProperty(exports, "validateRpcUrl", { enumerable: true, get: function () { return index_1.validateRpcUrl; } });
|
|
56
|
+
Object.defineProperty(exports, "ensurePublicHost", { enumerable: true, get: function () { return index_1.ensurePublicHost; } });
|
|
57
|
+
Object.defineProperty(exports, "testJsonRpc", { enumerable: true, get: function () { return index_1.testJsonRpc; } });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.testJsonRpc = exports.ensurePublicHost = exports.validateRpcUrl = void 0;
|
|
7
|
+
/** npm imports */
|
|
8
|
+
const promises_1 = __importDefault(require("dns/promises"));
|
|
9
|
+
const net_1 = __importDefault(require("net"));
|
|
10
|
+
const PRIVATE_IP_RANGES = [
|
|
11
|
+
/^127\./,
|
|
12
|
+
/^10\./,
|
|
13
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
14
|
+
/^192\.168\./,
|
|
15
|
+
/^169\.254\./,
|
|
16
|
+
];
|
|
17
|
+
const validateRpcUrl = (rpcUrl, allowLocal = false) => {
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(rpcUrl);
|
|
21
|
+
if (!allowLocal && ['localhost', '127.0.0.1', '::1'].includes(url.hostname))
|
|
22
|
+
throw new Error('Localhost URLs are not allowed');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error('Invalid RPC URL');
|
|
26
|
+
}
|
|
27
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(url.protocol))
|
|
28
|
+
throw new Error('Unsupported protocol. Only http, https, ws, and wss are allowed.');
|
|
29
|
+
if (['localhost', '127.0.0.1'].includes(url.hostname))
|
|
30
|
+
throw new Error('Localhost RPC not allowed');
|
|
31
|
+
return url;
|
|
32
|
+
};
|
|
33
|
+
exports.validateRpcUrl = validateRpcUrl;
|
|
34
|
+
const ensurePublicHost = async (rpcUrl) => {
|
|
35
|
+
const url = (0, exports.validateRpcUrl)(rpcUrl);
|
|
36
|
+
try {
|
|
37
|
+
const addresses = await promises_1.default.lookup(url.hostname, { all: true });
|
|
38
|
+
const hasPrivateIp = addresses
|
|
39
|
+
.filter((a) => net_1.default.isIP(a.address))
|
|
40
|
+
.some((a) => PRIVATE_IP_RANGES.some((r) => r.test(a.address)));
|
|
41
|
+
if (hasPrivateIp)
|
|
42
|
+
throw new Error('RPC URL resolves to a private IP address');
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('Error validating RPC URL:', error);
|
|
47
|
+
throw new Error('Failed to validate RPC URL');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.ensurePublicHost = ensurePublicHost;
|
|
51
|
+
const testJsonRpc = async (rpcUrl, method = 'eth_chainId', params = []) => {
|
|
52
|
+
await (0, exports.ensurePublicHost)(rpcUrl);
|
|
53
|
+
const payload = { jsonrpc: '2.0', id: 1, method, params };
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(rpcUrl, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify(payload),
|
|
59
|
+
signal: AbortSignal.timeout(5000),
|
|
60
|
+
});
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
+
throw new Error(`RPC test failed: ${errorMessage}`);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.testJsonRpc = testJsonRpc;
|
|
@@ -72,3 +72,38 @@ export interface FormatAmountOptions {
|
|
|
72
72
|
maxDisplayDigits?: number;
|
|
73
73
|
}
|
|
74
74
|
export type { TransactionRequest };
|
|
75
|
+
export interface GetBalanceParams {
|
|
76
|
+
walletAddress: string;
|
|
77
|
+
rpcUrl: string;
|
|
78
|
+
tokenAddress?: string;
|
|
79
|
+
chainId?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface GetBalancesChainRequest {
|
|
82
|
+
rpcUrl: string;
|
|
83
|
+
chainId?: number;
|
|
84
|
+
tokenAddresses: string[];
|
|
85
|
+
includeNative?: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface GetBalancesParams {
|
|
88
|
+
walletAddress: string;
|
|
89
|
+
chains: GetBalancesChainRequest[];
|
|
90
|
+
}
|
|
91
|
+
export interface TokenBalance {
|
|
92
|
+
tokenAddress: string | null;
|
|
93
|
+
balance: bigint | null;
|
|
94
|
+
error?: unknown;
|
|
95
|
+
}
|
|
96
|
+
export interface ChainBalances {
|
|
97
|
+
chainId?: number;
|
|
98
|
+
balances: TokenBalance[];
|
|
99
|
+
}
|
|
100
|
+
export interface GetBalanceResult {
|
|
101
|
+
walletAddress: string;
|
|
102
|
+
chains: ChainBalances[];
|
|
103
|
+
}
|
|
104
|
+
export interface ChainGroup {
|
|
105
|
+
rpcUrl: string;
|
|
106
|
+
chainId?: number;
|
|
107
|
+
nativeBalanceRequests: GetBalanceParams[];
|
|
108
|
+
tokenBalanceRequests: GetBalanceParams[];
|
|
109
|
+
}
|
|
@@ -6,9 +6,8 @@ const ethers_1 = require("ethers");
|
|
|
6
6
|
const formattedAmountForDisplay = (amount, decimals, options = {}) => {
|
|
7
7
|
const { decimalsToShow = 0, useGroupSeparator = true, locale = 'en-US', minimumFractionDigits, minDisplayDecimals = 9, maxDisplayDigits = 16, } = options;
|
|
8
8
|
const formattedAmount = (0, ethers_1.formatUnits)(amount, decimals);
|
|
9
|
-
if (formattedAmount.startsWith('-'))
|
|
9
|
+
if (formattedAmount.startsWith('-'))
|
|
10
10
|
throw new Error('Negative balances are not supported');
|
|
11
|
-
}
|
|
12
11
|
const [wholePart = '0', fractionPart = ''] = formattedAmount.split('.');
|
|
13
12
|
if (wholePart.length >= maxDisplayDigits) {
|
|
14
13
|
const mantissaWhole = wholePart[0] ?? '0';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const normalizeAddress: (address: string) => string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeAddress = void 0;
|
|
4
|
+
/** npm imports */
|
|
5
|
+
const ethers_1 = require("ethers");
|
|
6
|
+
const normalizeAddress = (address) => {
|
|
7
|
+
try {
|
|
8
|
+
return (0, ethers_1.getAddress)(address);
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
console.log('Error normalizing address:', e);
|
|
12
|
+
throw new Error('Error normalizing address: ' + (e instanceof Error ? e.message : String(e)));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
exports.normalizeAddress = normalizeAddress;
|
|
@@ -3,3 +3,6 @@ export const GAS_LIMIT_PER_TX_TYPE = {
|
|
|
3
3
|
DEFAULT_TRANSFER_ERC20: 65000n,
|
|
4
4
|
DEFAULT_APPROVAL: 100000n,
|
|
5
5
|
};
|
|
6
|
+
// & Multicall3 contract address (same on most chains)
|
|
7
|
+
// & https://www.multicall3.com/deployments
|
|
8
|
+
export const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import ERC20_TOKEN_CONTRACT_ABI from './ABIs/ERC20_TOKEN_CONTRACT_ABI';
|
|
3
3
|
export { ERC20_TOKEN_CONTRACT_ABI };
|
|
4
4
|
/** constants exports */
|
|
5
|
-
export { GAS_LIMIT_PER_TX_TYPE } from './constants/constants';
|
|
5
|
+
export { GAS_LIMIT_PER_TX_TYPE, MULTICALL3_ADDRESS } from './constants/constants';
|
|
6
6
|
/** basic blockchain exports */
|
|
7
7
|
export { buildMaxNativeTransferTx, buildUnsignedTransferTx, } from './blockchain/buildUnsignedTransferTx';
|
|
8
8
|
export { broadcastTransaction } from './blockchain/broadcastTransaction';
|
|
9
9
|
export { estimateGasLimitFromProvider } from './blockchain/estimateGasLimitFromProvider';
|
|
10
10
|
export { getProvider } from './blockchain/getProvider';
|
|
11
11
|
export { txStatus } from './blockchain/txStatus';
|
|
12
|
+
export { getBalance, getBalances } from './blockchain/getBalances';
|
|
12
13
|
/** services exports */
|
|
13
14
|
export { EvmWalletService, EvmGeneratedWallet, EvmDerivedWallet, } from './services/evm-wallet-core/evmWalletService';
|
|
14
15
|
export { EntropySource } from './services/evm-wallet-core/entropy';
|
|
@@ -20,5 +21,8 @@ import NATIVE_TOKENS from './utils/tokens';
|
|
|
20
21
|
export { NATIVE_TOKENS };
|
|
21
22
|
export { formattedAmountForDisplay, parsedAmount } from './utils/formatAmount';
|
|
22
23
|
export { handleErrorMessages, errorMessagesForBroadcast, errorMessagesForGasLimitEstimation, } from './utils/handleErrorMessages';
|
|
24
|
+
export { normalizeAddress } from './utils/normalizeAddress';
|
|
25
|
+
/** rpc exports */
|
|
26
|
+
export { validateRpcUrl, ensurePublicHost, testJsonRpc } from './rpc/index';
|
|
23
27
|
/** types exports */
|
|
24
|
-
export { BroadcastTransactionOptions, BuildMaxNativeTransferTxOptions, BuildMaxNativeTransferTxResponse, BuildUnsignedTransferTxOptions, EstimateGasLimitFromProviderProps, GasEstimateResult, TxStatusOptions, TxStatusResponse, UnsignedTransferTxResponse, FormatAmountOptions, TransactionRequest, } from './types/common';
|
|
28
|
+
export { BroadcastTransactionOptions, BuildMaxNativeTransferTxOptions, BuildMaxNativeTransferTxResponse, BuildUnsignedTransferTxOptions, EstimateGasLimitFromProviderProps, GasEstimateResult, TxStatusOptions, TxStatusResponse, UnsignedTransferTxResponse, FormatAmountOptions, TransactionRequest, GetBalanceParams, GetBalancesParams, GetBalancesChainRequest, GetBalanceResult, ChainBalances, TokenBalance, ChainGroup, } from './types/common';
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import ERC20_TOKEN_CONTRACT_ABI from './ABIs/ERC20_TOKEN_CONTRACT_ABI';
|
|
3
3
|
export { ERC20_TOKEN_CONTRACT_ABI };
|
|
4
4
|
/** constants exports */
|
|
5
|
-
export { GAS_LIMIT_PER_TX_TYPE } from './constants/constants';
|
|
5
|
+
export { GAS_LIMIT_PER_TX_TYPE, MULTICALL3_ADDRESS } from './constants/constants';
|
|
6
6
|
/** basic blockchain exports */
|
|
7
7
|
export { buildMaxNativeTransferTx, buildUnsignedTransferTx, } from './blockchain/buildUnsignedTransferTx';
|
|
8
8
|
export { broadcastTransaction } from './blockchain/broadcastTransaction';
|
|
9
9
|
export { estimateGasLimitFromProvider } from './blockchain/estimateGasLimitFromProvider';
|
|
10
10
|
export { getProvider } from './blockchain/getProvider';
|
|
11
11
|
export { txStatus } from './blockchain/txStatus';
|
|
12
|
+
export { getBalance, getBalances } from './blockchain/getBalances';
|
|
12
13
|
/** services exports */
|
|
13
14
|
export { EvmWalletService, } from './services/evm-wallet-core/evmWalletService';
|
|
14
15
|
export { createWallet, signMessage, signTransaction } from './services/evm-wallet-core/signer';
|
|
@@ -19,3 +20,6 @@ import NATIVE_TOKENS from './utils/tokens';
|
|
|
19
20
|
export { NATIVE_TOKENS };
|
|
20
21
|
export { formattedAmountForDisplay, parsedAmount } from './utils/formatAmount';
|
|
21
22
|
export { handleErrorMessages, errorMessagesForBroadcast, errorMessagesForGasLimitEstimation, } from './utils/handleErrorMessages';
|
|
23
|
+
export { normalizeAddress } from './utils/normalizeAddress';
|
|
24
|
+
/** rpc exports */
|
|
25
|
+
export { validateRpcUrl, ensurePublicHost, testJsonRpc } from './rpc/index';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** npm imports */
|
|
2
|
+
import dns from 'dns/promises';
|
|
3
|
+
import net from 'net';
|
|
4
|
+
const PRIVATE_IP_RANGES = [
|
|
5
|
+
/^127\./,
|
|
6
|
+
/^10\./,
|
|
7
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
8
|
+
/^192\.168\./,
|
|
9
|
+
/^169\.254\./,
|
|
10
|
+
];
|
|
11
|
+
export const validateRpcUrl = (rpcUrl, allowLocal = false) => {
|
|
12
|
+
let url;
|
|
13
|
+
try {
|
|
14
|
+
url = new URL(rpcUrl);
|
|
15
|
+
if (!allowLocal && ['localhost', '127.0.0.1', '::1'].includes(url.hostname))
|
|
16
|
+
throw new Error('Localhost URLs are not allowed');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error('Invalid RPC URL');
|
|
20
|
+
}
|
|
21
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(url.protocol))
|
|
22
|
+
throw new Error('Unsupported protocol. Only http, https, ws, and wss are allowed.');
|
|
23
|
+
if (['localhost', '127.0.0.1'].includes(url.hostname))
|
|
24
|
+
throw new Error('Localhost RPC not allowed');
|
|
25
|
+
return url;
|
|
26
|
+
};
|
|
27
|
+
export const ensurePublicHost = async (rpcUrl) => {
|
|
28
|
+
const url = validateRpcUrl(rpcUrl);
|
|
29
|
+
try {
|
|
30
|
+
const addresses = await dns.lookup(url.hostname, { all: true });
|
|
31
|
+
const hasPrivateIp = addresses
|
|
32
|
+
.filter((a) => net.isIP(a.address))
|
|
33
|
+
.some((a) => PRIVATE_IP_RANGES.some((r) => r.test(a.address)));
|
|
34
|
+
if (hasPrivateIp)
|
|
35
|
+
throw new Error('RPC URL resolves to a private IP address');
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Error validating RPC URL:', error);
|
|
40
|
+
throw new Error('Failed to validate RPC URL');
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const testJsonRpc = async (rpcUrl, method = 'eth_chainId', params = []) => {
|
|
44
|
+
await ensurePublicHost(rpcUrl);
|
|
45
|
+
const payload = { jsonrpc: '2.0', id: 1, method, params };
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(rpcUrl, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: { 'Content-Type': 'application/json' },
|
|
50
|
+
body: JSON.stringify(payload),
|
|
51
|
+
signal: AbortSignal.timeout(5000),
|
|
52
|
+
});
|
|
53
|
+
const data = await res.json();
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
58
|
+
throw new Error(`RPC test failed: ${errorMessage}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
package/dist/types/common.d.ts
CHANGED
|
@@ -72,3 +72,38 @@ export interface FormatAmountOptions {
|
|
|
72
72
|
maxDisplayDigits?: number;
|
|
73
73
|
}
|
|
74
74
|
export type { TransactionRequest };
|
|
75
|
+
export interface GetBalanceParams {
|
|
76
|
+
walletAddress: string;
|
|
77
|
+
rpcUrl: string;
|
|
78
|
+
tokenAddress?: string;
|
|
79
|
+
chainId?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface GetBalancesChainRequest {
|
|
82
|
+
rpcUrl: string;
|
|
83
|
+
chainId?: number;
|
|
84
|
+
tokenAddresses: string[];
|
|
85
|
+
includeNative?: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface GetBalancesParams {
|
|
88
|
+
walletAddress: string;
|
|
89
|
+
chains: GetBalancesChainRequest[];
|
|
90
|
+
}
|
|
91
|
+
export interface TokenBalance {
|
|
92
|
+
tokenAddress: string | null;
|
|
93
|
+
balance: bigint | null;
|
|
94
|
+
error?: unknown;
|
|
95
|
+
}
|
|
96
|
+
export interface ChainBalances {
|
|
97
|
+
chainId?: number;
|
|
98
|
+
balances: TokenBalance[];
|
|
99
|
+
}
|
|
100
|
+
export interface GetBalanceResult {
|
|
101
|
+
walletAddress: string;
|
|
102
|
+
chains: ChainBalances[];
|
|
103
|
+
}
|
|
104
|
+
export interface ChainGroup {
|
|
105
|
+
rpcUrl: string;
|
|
106
|
+
chainId?: number;
|
|
107
|
+
nativeBalanceRequests: GetBalanceParams[];
|
|
108
|
+
tokenBalanceRequests: GetBalanceParams[];
|
|
109
|
+
}
|
|
@@ -3,9 +3,8 @@ import { formatUnits, parseUnits } from 'ethers';
|
|
|
3
3
|
export const formattedAmountForDisplay = (amount, decimals, options = {}) => {
|
|
4
4
|
const { decimalsToShow = 0, useGroupSeparator = true, locale = 'en-US', minimumFractionDigits, minDisplayDecimals = 9, maxDisplayDigits = 16, } = options;
|
|
5
5
|
const formattedAmount = formatUnits(amount, decimals);
|
|
6
|
-
if (formattedAmount.startsWith('-'))
|
|
6
|
+
if (formattedAmount.startsWith('-'))
|
|
7
7
|
throw new Error('Negative balances are not supported');
|
|
8
|
-
}
|
|
9
8
|
const [wholePart = '0', fractionPart = ''] = formattedAmount.split('.');
|
|
10
9
|
if (wholePart.length >= maxDisplayDigits) {
|
|
11
10
|
const mantissaWhole = wholePart[0] ?? '0';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const normalizeAddress: (address: string) => string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** npm imports */
|
|
2
|
+
import { getAddress } from 'ethers';
|
|
3
|
+
export const normalizeAddress = (address) => {
|
|
4
|
+
try {
|
|
5
|
+
return getAddress(address);
|
|
6
|
+
}
|
|
7
|
+
catch (e) {
|
|
8
|
+
console.log('Error normalizing address:', e);
|
|
9
|
+
throw new Error('Error normalizing address: ' + (e instanceof Error ? e.message : String(e)));
|
|
10
|
+
}
|
|
11
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octaflowlabs/onchain-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "onchain methods for web3",
|
|
5
5
|
"repository": "https://github.com/crisramb665/onchain-sdk.git",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"prepublishOnly": "yarn run build"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
+
"@types/node": "^25.2.2",
|
|
19
20
|
"eslint": "^9.39.2",
|
|
20
21
|
"eslint-config-prettier": "^10.1.8",
|
|
21
22
|
"eslint-plugin-prettier": "^5.5.5",
|