Skip to content
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\](?!(axios|is-retry-allowed)).+\\.(js|jsx)$'],
testTimeout: 1000 * 30,
};
3 changes: 3 additions & 0 deletions src/client/mixin-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import merge from 'lodash.merge';
import type { AxiosInstance } from 'axios';
import { validate } from 'uuid';
import type Keystore from './types/keystore';
import type { HTTPConfig, RequestClient } from './types/client';
import { createAxiosClient, createRequestClient } from './utils/client';
Expand Down Expand Up @@ -52,6 +53,8 @@ export function MixinApi(config: HTTPConfig = {}): KeystoreClientReturnType & Re
const requestClient = createRequestClient(axiosInstance);

const { keystore } = config;
if (keystore && !keystore.user_id && keystore.client_id && validate(keystore.client_id)) keystore.user_id = keystore.client_id;

const keystoreClient = KeystoreClient(axiosInstance, keystore, config);

return merge(keystoreClient, requestClient);
Expand Down
2 changes: 1 addition & 1 deletion src/client/types/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Keystore from './keystore';
import { BlazeOptions } from './blaze';

export interface RequestConfig
extends Pick<AxiosRequestConfig, 'baseURL' | 'headers' | 'timeout' | 'httpAgent' | 'httpsAgent' | 'onDownloadProgress' | 'onUploadProgress' | 'proxy'> {
extends Partial<Pick<AxiosRequestConfig, 'baseURL' | 'headers' | 'timeout' | 'httpAgent' | 'httpsAgent' | 'onDownloadProgress' | 'onUploadProgress' | 'proxy'>> {
responseCallback?: (rep: unknown) => void;
retry?: number;
}
Expand Down
1 change: 1 addition & 0 deletions src/client/types/keystore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Keystore {
client_id?: string;
user_id: string;
private_key: string;

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './client';
export * from './mainnet';
export * from './mvm';
export * from './webview';
export * from './constant';
97 changes: 97 additions & 0 deletions src/mainnet/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import axios, { AxiosResponse } from 'axios';
import axiosRetry, { isIdempotentRequestError } from 'axios-retry';
import isRetryAllowed from 'is-retry-allowed';
import type { RequestConfig } from '../client';
import type {
NodeInfoRpcResponse,
SyncPoint,
SendRawTransactionRpcResponse,
TransactionRpcResponse,
UTXORpcResponse,
KeyRpcResponse,
SnapshotRpcResponse,
MintWorkRpcResponse,
MintDistributionRpcResponse,
NodeRpcResponse,
RoundRpcResponse,
RoundLinkRpcResponse,
} from './type';

axios.defaults.headers.post['Content-Type'] = 'application/json';

export const MixinMainnetRPC = 'https://rpc.mixin.dev';

export const MainnetRpcClient = (config: RequestConfig = {}) => {
const timeout = config?.timeout || 1000 * 10;
const retries = config?.retry || 5;

const ins = axios.create({
...config,
timeout,
baseURL: MixinMainnetRPC,
});

ins.interceptors.response.use(async (res: AxiosResponse) => {
const { data, error } = res.data;
if (error) throw new Error(error);
return data;
});

ins.interceptors.response.use(undefined, async (e: any) => {
await config?.responseCallback?.(e);

return Promise.reject(e);
});

axiosRetry(ins, {
retries,
shouldResetTimeout: true,
retryDelay: () => 500,
retryCondition: error =>
(!error.response &&
Boolean(error.code) && // Prevents retrying cancelled requests
isRetryAllowed(error)) ||
isIdempotentRequestError(error),
});

return {
getInfo: (id = '1'): Promise<NodeInfoRpcResponse> => ins.post<unknown, NodeInfoRpcResponse>('/', { id, method: 'getinfo', params: [] }),

dumpGraphHead: (id = '1'): Promise<SyncPoint[]> => ins.post<unknown, SyncPoint[]>('/', { id, method: 'dumpgraphhead', params: [] }),

sendRawTransaction: (raw: string, id = '1'): Promise<SendRawTransactionRpcResponse> =>
ins.post<unknown, SendRawTransactionRpcResponse>('/', { id, method: 'sendrawtransaction', params: [raw] }),

getTransaction: (hash: string, id = '1'): Promise<TransactionRpcResponse> => ins.post<unknown, TransactionRpcResponse>('/', { id, method: 'gettransaction', params: [hash] }),

getCacheTransaction: (hash: string, id = '1'): Promise<TransactionRpcResponse> =>
ins.post<unknown, TransactionRpcResponse>('/', { id, method: 'getcachetransaction', params: [hash] }),

getUTXO: (hash: string, index: string, id = '1'): Promise<UTXORpcResponse> => ins.post<unknown, UTXORpcResponse>('/', { id, method: 'getutxo', params: [hash, index] }),

getKey: (key: string, id = '1'): Promise<KeyRpcResponse> => ins.post<unknown, KeyRpcResponse>('/', { id, method: 'getkey', params: [key] }),

getSnapshot: (hash: string, id = '1'): Promise<SnapshotRpcResponse> => ins.post<unknown, SnapshotRpcResponse>('/', { id, method: 'getsnapshot', params: [hash] }),

listSnapshots: (offset: string, count: string, sig: boolean, tx: boolean, id = '1'): Promise<SnapshotRpcResponse[]> =>
ins.post<unknown, SnapshotRpcResponse[]>('/', { id, method: 'listsnapshots', params: [offset, count, sig, tx] }),

listMintWorks: (offset: string, id = '1'): Promise<MintWorkRpcResponse[]> => ins.post<unknown, MintWorkRpcResponse[]>('/', { id, method: 'listmintworks', params: [offset] }),

listMintDistributions: (offset: string, count: string, tx: boolean, id = '1'): Promise<MintDistributionRpcResponse[]> =>
ins.post<unknown, MintDistributionRpcResponse[]>('/', { id, method: 'listmintdistributions', params: [offset, count, tx] }),

listAllNodes: (threshold: string, state?: boolean, id = '1'): Promise<NodeRpcResponse[]> =>
ins.post<unknown, NodeRpcResponse[]>('/', { id, method: 'listallnodes', params: [threshold, state] }),

getRoundByNumber: (node: string, number: string, id = '1'): Promise<RoundRpcResponse> =>
ins.post<unknown, RoundRpcResponse>('/', { id, method: 'getroundbynumber', params: [node, number] }),

getRoundByHash: (hash: string, id = '1'): Promise<RoundRpcResponse> => ins.post<unknown, RoundRpcResponse>('/', { id, method: 'getroundbyhash', params: [hash] }),

getRoundLink: (from: string, to: string, id = '1'): Promise<RoundLinkRpcResponse> =>
ins.post<unknown, RoundLinkRpcResponse>('/', { id, method: 'getroundlink', params: [from, to] }),
};
};

export default MainnetRpcClient;
2 changes: 2 additions & 0 deletions src/mainnet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './client';
export * from './type';
190 changes: 190 additions & 0 deletions src/mainnet/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { Input, Output } from '../mvm/types/encoder';

type NodeState = 'PLEDGING' | 'ACCEPTED' | 'REMOVED' | 'CANCELLED';

type References = null | {
external: string;
self: string;
};

export interface NodeInfoRpcResponse {
network: string;
node: string;
version: string;
uptime: string;
epoch: string;
timestamp: string;

mint?: Mint;
graph?: Gragh;
queue?: Queue;
metric?: Metric;
}

export interface SyncPoint {
node: string;
round: number;
hash: string;
pool: {
count: number;
index: number;
};
}

export interface SendRawTransactionRpcResponse {
hash: string;
}

export interface TransactionRpcResponse {
version: number;
asset: string;
inputs: Input[];
outputs: Output[];
extra: string;
hash: string;
hex: string;
snapshot?: string;
}

export interface UTXORpcResponse {
type: string;
hash: string;
index: number;
amount: string;

keys?: string[];
script?: string;
mask?: string;
lock?: string;
}

export interface KeyRpcResponse {
transaction: string;
}

export interface SnapshotRpcResponse {
version: number;
node: string;
references: References;
round: number;
timestamp: number;
hash: string;
hex?: string;
topology?: number;
witness?: {
signature: string;
timestamp: number;
};

transaction: TransactionRpcResponse | string;
transactions?: TransactionRpcResponse[] | string[];

signature?: string;
signatures?: string[];
}

export interface MintWorkRpcResponse {
[key: string]: number[];
}

export interface MintDistributionRpcResponse {
batch: number;
group: string;
amount: string;
transaction: TransactionRpcResponse | string;
}

export interface NodeRpcResponse {
id: string;
payee: string;
signer: string;
state: NodeState;
timestamp: number;
transaction: string;
}

export interface RoundRpcResponse {
node: string;
hash: string;
start: number;
end: number;
number: number;
references: References;
snapshots: SnapshotRpcResponse[];
}

export interface RoundLinkRpcResponse {
link: number;
}

interface Mint {
pool: string;
batch: number;
pledge: string;
}

interface Gragh {
/** node state is 'PLEDGING' | 'ACCEPTED' */
consensus: Node[];
cache: {
[key: string]: CacheGraph;
};
final: {
[key: string]: FinalGraph;
};
sps: number;
topology: number;
tps: number;
}

interface CacheGraph {
node: string;
round: number;
timestamp: number;
snapshots: SnapshotRpcResponse[];
references: References;
}

interface FinalGraph {
hash: string;
node: string;
round: number;
start: number;
end: number;
}

interface Queue {
finals: number;
caches: number;
/** key is chain id */
state: {
[key: string]: number[];
};
}

interface Metric {
transport: {
received: MetricPool;
sent: MetricPool;
};
}

interface MetricPool {
ping: number;
authentication: number;
graph: number;
'snapshot-confirm': number;
'transaction-request': number;
transaction: number;

'snapshot-announcement': number;
'snapshot-commitment': number;
'transaciton-challenge': number;
'snapshot-response': number;
'snapshot-finalization': number;
commitments: number;
'full-challenge': number;

bundle: number;
'gossip-neighbors': number;
}
1 change: 0 additions & 1 deletion src/mvm/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ export * from './encoder';
export * from './extra';
export * from './payment';
export * from './registry';
export * from './transaction';
export * from './value';
19 changes: 0 additions & 19 deletions src/mvm/types/transaction.ts

This file was deleted.

31 changes: 31 additions & 0 deletions test/mixin/rpc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MainnetRpcClient } from '../../src';

const client = MainnetRpcClient();

describe('Tests for rpc', () => {
test('Test for rpc', async () => {
const nodeInfo = await client.getInfo();
expect(nodeInfo.node).toEqual('3bdca8b8a593d9c5882532735622170bce2a1452d47cdeee5f950800593ef0bb');

const utxo = await client.getUTXO('fb4f927f3f04bdf34fcd26056e84214edb7e8c9737ddf4c7e6829caaf8e54e27', '1');
expect(utxo.hash).toEqual('fb4f927f3f04bdf34fcd26056e84214edb7e8c9737ddf4c7e6829caaf8e54e27');

const tx = await client.getTransaction('1453961d4f880775fa94c604b630a3662e99d83187b8f33e25e59d9ae4d8c4b6');
expect(tx.hash).toEqual('1453961d4f880775fa94c604b630a3662e99d83187b8f33e25e59d9ae4d8c4b6');

const key = await client.getKey('29ecfd9fb1eca4d9375da58fd526ae91b22a244d6ee48b81f8f7915530239324');
expect(key.transaction).toEqual('1453961d4f880775fa94c604b630a3662e99d83187b8f33e25e59d9ae4d8c4b6');

const snapshot = await client.getSnapshot('2714982c8c675f42f1198af2c8d1c5f7444b4f42abf75e4ec7c1e541802e47d7');
expect(snapshot.hash).toEqual('2714982c8c675f42f1198af2c8d1c5f7444b4f42abf75e4ec7c1e541802e47d7');

const snapshots = await client.listSnapshots('0', '10', true, true);
expect(snapshots.length).toEqual(10);

const roundByNumber = await client.getRoundByNumber('f3aa49c73bd64cec574aad27870968c8c3be40d0537040ff0252f35b8507de3f', '362153');
expect(roundByNumber.number).toEqual(362153);

const roundByHash = await client.getRoundByHash('5691c08eef98d40e2efb3a770c0257e1ffb45e480184a4349c9423ca2ac9fd30');
expect(roundByHash.number).toEqual(362153);
});
});