Skip to content

Commit

Permalink
feat: snip20 token info batch query
Browse files Browse the repository at this point in the history
  • Loading branch information
AustinWoetzel committed Feb 13, 2024
1 parent 21669fc commit 1344e21
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/slow-carpets-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shadeprotocol/shadejs": patch
---

snip20 token info batch query
80 changes: 80 additions & 0 deletions docs/queries/snip20.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,86 @@ console.log(output)
```

## Batch Tokens Info

**input**

```ts
function batchQuerySnip20TokensInfo({
queryRouterContractAddress,
queryRouterCodeHash,
lcdEndpoint,
chainId,
tokenContracts,
}:{
queryRouterContractAddress: string,
queryRouterCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
tokenContracts: Contract[]
})
```

**output**

```ts
type BatchTokensInfo = BatchTokensInfoItem[]
type TokenInfo = {
name: string,
symbol: string,
decimals: number,
totalSupply: string,
}
type BatchTokensInfoItem = {
tokenContractAddress: string,
tokenInfo: TokenInfo,
}
```

**example use**

```ts
const output = await batchQuerySnip20TokensInfo = ({
queryRouterContractAddress: '[QUERY_ROUTER_CONTRACT_ADDRESS]',
queryRouterCodeHash: '[QUERY_ROUTER_CODE_HASH]',
tokenContracts: [{
address: '[TOKEN_1_ADDRESS]',
codeHash: '[TOKEN_1_CODE_HASH]',
},
{
address: '[TOKEN_2_ADDRESS]',
codeHash: '[TOKEN_2_CODE_HASH]',
}]
})
console.log(output)
```
***console***
```md
[
{
tokenContractAddress: 'secret153wu605vvp934xhd4k9dtd640zsep5jkesstdm',
tokenInfo: {
name: 'Shade',
symbol: 'SHD',
decimals: 8,
totalSupply: '248115955993665',
},
},
{
tokenContractAddress: 'secret1fl449muk5yq8dlad7a22nje4p5d2pnsgymhjfd',
tokenInfo: {
name: 'Silk',
symbol: 'SILK',
decimals: 6,
totalSupply: '3688426236358',
},
},
];
```

## Get Balance

**input**
Expand Down
76 changes: 75 additions & 1 deletion src/contracts/services/snip20.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
querySnip20TokenInfo,
querySnip20Balance$,
querySnip20Balance,
parseBatchQueryTokensInfo,
batchQuerySnip20TokensInfo,
batchQuerySnip20TokensInfo$,
} from '~/contracts/services/snip20';
import {
test,
Expand All @@ -14,10 +17,13 @@ import {
} from 'vitest';
import tokenInfoResponse from '~/test/mocks/snip20/tokenInfoResponse.json';
import { tokenInfoParsed } from '~/test/mocks/snip20/tokenInfoParsed';
import { batchTokensInfoParsed } from '~/test/mocks/snip20/batchQueryTokensInfoParsed';
import { batchTokensInfoUnparsed } from '~/test/mocks/snip20/batchQueryTokensInfoUnparsed';
import { of } from 'rxjs';
import balanceResponse from '~/test/mocks/snip20/balanceResponse.json';

const sendSecretClientContractQuery$ = vi.hoisted(() => vi.fn());
const batchQuery$ = vi.hoisted(() => vi.fn());

beforeAll(() => {
vi.mock('~/contracts/definitions/snip20', () => ({
Expand All @@ -36,9 +42,13 @@ beforeAll(() => {
vi.mock('~/client/services/clientServices', () => ({
sendSecretClientContractQuery$,
}));

vi.mock('~/contracts/services/batchQuery', () => ({
batchQuery$,
}));
});

test('it can parse the response snip20 token info query', () => {
test('it can parse the response snip20 balance query', () => {
expect(parseBalance(
balanceResponse,
)).toStrictEqual('123');
Expand All @@ -50,6 +60,12 @@ test('it can parse the response snip20 token info query', () => {
)).toStrictEqual(tokenInfoParsed);
});

test('it can parse the batch snip20 token info query', () => {
expect(parseBatchQueryTokensInfo(
batchTokensInfoUnparsed,
)).toStrictEqual(batchTokensInfoParsed);
});

test('it can call the snip20 token info query', async () => {
const input = {
snip20ContractAddress: 'CONTRACT_ADDRESS',
Expand Down Expand Up @@ -90,6 +106,64 @@ test('it can call the snip20 token info query', async () => {
expect(response).toStrictEqual(tokenInfoParsed);
});

test('it can call the batch snip20 token info query', async () => {
const input = {
queryRouterContractAddress: 'CONTRACT_ADDRESS',
queryRouterCodeHash: 'CODE_HASH',
lcdEndpoint: 'LCD_ENDPOINT',
chainId: 'CHAIN_ID',
tokenContracts: [{
address: 'PAIR_ADDRESS',
codeHash: 'PAIR_CODE_HASH',
}],
};

// observables function
batchQuery$.mockReturnValueOnce(of(batchTokensInfoUnparsed));
let output;
batchQuerySnip20TokensInfo$(input).subscribe({
next: (response) => {
output = response;
},
});

expect(batchQuery$).toHaveBeenCalledWith({
contractAddress: input.queryRouterContractAddress,
codeHash: input.queryRouterCodeHash,
lcdEndpoint: input.lcdEndpoint,
chainId: input.chainId,
queries: [{
id: input.tokenContracts[0].address,
contract: {
address: input.tokenContracts[0].address,
codeHash: input.tokenContracts[0].codeHash,
},
queryMsg: 'TOKEN_INFO_MSG',
}],
});

expect(output).toStrictEqual(batchTokensInfoParsed);

// async/await function
batchQuery$.mockReturnValueOnce(of(batchTokensInfoUnparsed));
const response = await batchQuerySnip20TokensInfo(input);
expect(batchQuery$).toHaveBeenCalledWith({
contractAddress: input.queryRouterContractAddress,
codeHash: input.queryRouterCodeHash,
lcdEndpoint: input.lcdEndpoint,
chainId: input.chainId,
queries: [{
id: input.tokenContracts[0].address,
contract: {
address: input.tokenContracts[0].address,
codeHash: input.tokenContracts[0].codeHash,
},
queryMsg: 'TOKEN_INFO_MSG',
}],
});
expect(response).toStrictEqual(batchTokensInfoParsed);
});

test('it can call the snip20 balance query', async () => {
const input = {
snip20ContractAddress: 'CONTRACT_ADDRESS',
Expand Down
83 changes: 82 additions & 1 deletion src/contracts/services/snip20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import {
TokenInfoResponse,
BalanceResponse,
} from '~/types/contracts/snip20/response';
import { TokenInfo } from '~/types/contracts/snip20/model';
import { TokenInfo, BatchTokensInfo } from '~/types/contracts/snip20/model';
import {
Contract,
BatchQueryParams,
BatchQueryParsedResponse,
} from '~/types';
import { batchQuery$ } from './batchQuery';

const parseTokenInfo = (response: TokenInfoResponse): TokenInfo => ({
name: response.token_info.name,
Expand All @@ -20,6 +26,17 @@ const parseTokenInfo = (response: TokenInfoResponse): TokenInfo => ({
totalSupply: response.token_info.total_supply,
});

/**
* parses the token info reponse from a batch query of
* multiple token contracts
*/
const parseBatchQueryTokensInfo = (
response: BatchQueryParsedResponse,
): BatchTokensInfo => response.map((item) => ({
tokenContractAddress: item.id as string,
tokenInfo: parseTokenInfo(item.response),
}));

const parseBalance = (response: BalanceResponse): string => response.balance.amount;

/**
Expand Down Expand Up @@ -68,6 +85,67 @@ async function querySnip20TokenInfo({
}));
}

/**
* query the info for multiple tokens at one time
*/
function batchQuerySnip20TokensInfo$({
queryRouterContractAddress,
queryRouterCodeHash,
lcdEndpoint,
chainId,
tokenContracts,
}:{
queryRouterContractAddress: string,
queryRouterCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
tokenContracts: Contract[]
}) {
const queries:BatchQueryParams[] = tokenContracts.map((contract) => ({
id: contract.address,
contract: {
address: contract.address,
codeHash: contract.codeHash,
},
queryMsg: snip20.queries.tokenInfo(),
}));
return batchQuery$({
contractAddress: queryRouterContractAddress,
codeHash: queryRouterCodeHash,
lcdEndpoint,
chainId,
queries,
}).pipe(
map(parseBatchQueryTokensInfo),
first(),
);
}

/**
* query the info for multiple tokens at one time
*/
function batchQuerySnip20TokensInfo({
queryRouterContractAddress,
queryRouterCodeHash,
lcdEndpoint,
chainId,
tokenContracts,
}:{
queryRouterContractAddress: string,
queryRouterCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
tokenContracts: Contract[]
}) {
return lastValueFrom(batchQuerySnip20TokensInfo$({
queryRouterContractAddress,
queryRouterCodeHash,
lcdEndpoint,
chainId,
tokenContracts,
}));
}

/**
* query a user's private snip20 balance using a viewing key
*/
Expand Down Expand Up @@ -131,4 +209,7 @@ export {
querySnip20TokenInfo,
querySnip20Balance$,
querySnip20Balance,
parseBatchQueryTokensInfo,
batchQuerySnip20TokensInfo$,
batchQuerySnip20TokensInfo,
};
26 changes: 26 additions & 0 deletions src/test/mocks/snip20/batchQueryTokensInfoParsed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BatchTokensInfo } from '~/types';

const batchTokensInfoParsed: BatchTokensInfo = [
{
tokenContractAddress: 'secret153wu605vvp934xhd4k9dtd640zsep5jkesstdm',
tokenInfo: {
name: 'Shade',
symbol: 'SHD',
decimals: 8,
totalSupply: '248115955993665',
},
},
{
tokenContractAddress: 'secret1fl449muk5yq8dlad7a22nje4p5d2pnsgymhjfd',
tokenInfo: {
name: 'Silk',
symbol: 'SILK',
decimals: 6,
totalSupply: '3688426236358',
},
},
];

export {
batchTokensInfoParsed,
};
28 changes: 28 additions & 0 deletions src/test/mocks/snip20/batchQueryTokensInfoUnparsed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const batchTokensInfoUnparsed = [
{
id: 'secret153wu605vvp934xhd4k9dtd640zsep5jkesstdm',
response: {
token_info: {
name: 'Shade',
symbol: 'SHD',
decimals: 8,
total_supply: '248115955993665',
},
},
},
{
id: 'secret1fl449muk5yq8dlad7a22nje4p5d2pnsgymhjfd',
response: {
token_info: {
name: 'Silk',
symbol: 'SILK',
decimals: 6,
total_supply: '3688426236358',
},
},
},
];

export {
batchTokensInfoUnparsed,
};
8 changes: 8 additions & 0 deletions src/types/contracts/snip20/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ type TokenInfo = {
totalSupply: string,
}

type BatchTokensInfoItem = {
tokenContractAddress: string,
tokenInfo: TokenInfo,
}

type BatchTokensInfo = BatchTokensInfoItem[]

export type {
HandleMsg,
Snip20MessageRequest,
TokenInfo,
BatchTokensInfo,
};

0 comments on commit 1344e21

Please sign in to comment.