Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ChaingraphNetworkProvider #263

Open
wants to merge 3 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cashscript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@bitauth/libauth": "^3.1.0-next.2",
"@cashscript/utils": "^0.11.0-next.0",
"@mr-zwets/bchn-api-wrapper": "^1.0.1",
"chaingraph-ts": "^0.2.3",
"delay": "^6.0.0",
"electrum-cash": "^2.0.10",
"fast-deep-equal": "^3.1.3",
Expand Down
1 change: 1 addition & 0 deletions packages/cashscript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ export {
ElectrumNetworkProvider,
FullStackNetworkProvider,
MockNetworkProvider,
ChaingraphNetworkProvider,
} from './network/index.js';
export { randomUtxo, randomToken, randomNFT } from './utils.js';
60 changes: 60 additions & 0 deletions packages/cashscript/src/network/ChaingraphNetworkProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ChaingraphClient } from 'chaingraph-ts';
import { Utxo, Network } from '../interfaces.js';
import NetworkProvider from './NetworkProvider.js';

export default class ChaingraphNetworkProvider implements NetworkProvider {
private client: ChaingraphClient;

constructor(
public network: Network,
chaingraphOrUrl: ChaingraphClient | string
) {
if (chaingraphOrUrl instanceof ChaingraphClient) {
this.client = chaingraphOrUrl;
} else if (typeof chaingraphOrUrl === 'string') {
this.client = new ChaingraphClient(chaingraphOrUrl);
} else {
throw new Error(
'Invalid parameter. Must be an instance of ChaingraphClient or a chaingraph url'
);
}
}

async getUtxos(address: string): Promise<Utxo[]> {
const result = await this.client.getUtxosForAddress(address);

const utxos: Utxo[] = result.map((utxo) => ({
txid: utxo.transaction_hash,
vout: Number(utxo.output_index),
satoshis: BigInt(utxo.value_satoshis),
token: utxo.token_category
? {
category: utxo.token_category,
amount: BigInt(utxo.fungible_token_amount!),
nft: utxo.nonfungible_token_commitment
? {
capability: utxo.nonfungible_token_capability!,
commitment: utxo.nonfungible_token_commitment,
}
: undefined,
}
: undefined,
}));

return utxos;
}

async getBlockHeight(): Promise<number> {
return this.client.getBlockHeight();
}

async getRawTransaction(txid: string): Promise<string> {
const response = await this.client.getRawTransaction(txid);
return response!;
}

async sendRawTransaction(txHex: string): Promise<string> {
const response = await this.client.sendRawTransaction(txHex);
return response.send_transaction.transaction_hash;
}
}
1 change: 1 addition & 0 deletions packages/cashscript/src/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvide
export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
export { default as MockNetworkProvider } from './MockNetworkProvider.js';
export { default as ChaingraphNetworkProvider } from './ChaingraphNetworkProvider.js';
75 changes: 75 additions & 0 deletions packages/cashscript/test/e2e/network/Chaingraph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Contract, SignatureTemplate, ChaingraphNetworkProvider } from '../../../src/index.js';
import {
alicePriv,
bobPkh,
bobPriv,
bobPub,
} from '../../fixture/vars.js';
import { getTxOutputs } from '../../test-util.js';
import { FailedRequireError } from '../../../src/Errors.js';
import artifact from '../../fixture/p2pkh.json' with { type: 'json' };

const describeOrSkip = process.env.TESTS_USE_MOCKNET ? describe.skip : describe;

describeOrSkip('test ChaingraphNetworkProvider', () => {
const provider = new ChaingraphNetworkProvider('mainnet', "https://gql.chaingraph.pat.mn/v1/graphql");

describe('get utxos using ChaingraphNetworkProvider', () => {
it('should get the utxos for a p2sh20 contract', async () => {
// Note: We instantiate the contract with bobPkh to avoid mempool conflicts with other tests
const p2pkhInstance = new Contract(artifact, [bobPkh], { provider, addressType: 'p2sh20' });
console.log(p2pkhInstance.address);

const utxos = await p2pkhInstance.getUtxos();
expect(Array.isArray(utxos)).toBe(true);
});
it('should get the utxos for a p2sh32 contract', async () => {
// Note: We instantiate the contract with bobPkh to avoid mempool conflicts with other tests
const p2pkhInstance = new Contract(artifact, [bobPkh], { provider, addressType: 'p2sh32' });
console.log(p2pkhInstance.address);

const utxos = await p2pkhInstance.getUtxos();
expect(Array.isArray(utxos)).toBe(true);
});
});

describe('send using ChaingraphNetworkProvider', () => {
// Note: We instantiate the contract with bobPkh to avoid mempool conflicts with other tests
// Using p2sh20 address because it is funded on mainnet
const p2pkhInstance = new Contract(artifact, [bobPkh], { provider, addressType: 'p2sh20' });
console.log(p2pkhInstance.address);

it('should fail when using incorrect function arguments', async () => {
// given
const to = p2pkhInstance.address;
const amount = 10000n;

// when
const txPromise = p2pkhInstance.functions
.spend(bobPub, new SignatureTemplate(alicePriv))
.to(to, amount)
.send();

// then
await expect(txPromise).rejects.toThrow(FailedRequireError);
await expect(txPromise).rejects.toThrow('P2PKH.cash:5 Require statement failed at input 0 in contract P2PKH.cash at line 5.');
});

it('should succeed when using correct function arguments', async () => {
// given
const to = p2pkhInstance.address;
const amount = 10000n;

// when
const tx = await p2pkhInstance.functions
.spend(bobPub, new SignatureTemplate(bobPriv))
.to(to, amount)
.send();

// then
const txOutputs = getTxOutputs(tx, 'mainnet');
expect(txOutputs).toEqual(expect.arrayContaining([{ to, amount }]));
expect(tx.txid).toBeDefined();
});
});
});
78 changes: 78 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
# yarn lockfile v1


"@0no-co/graphql.web@^1.0.5":
version "1.0.13"
resolved "https://registry.yarnpkg.com/@0no-co/graphql.web/-/graphql.web-1.0.13.tgz#978f4d3a869240f2d487fa1c1009028b34bc33b5"
integrity sha512-jqYxOevheVTU1S36ZdzAkJIdvRp2m3OYIG5SEoKDw5NI8eVwkoI0D/Q3DYNGmXCxkA6CQuoa7zvMiDPTLqUNuw==

"@0no-co/graphqlsp@^1.12.13":
version "1.12.16"
resolved "https://registry.yarnpkg.com/@0no-co/graphqlsp/-/graphqlsp-1.12.16.tgz#58fe7bad53b3ad9fdf2d5f41ddeb9b418d289a03"
integrity sha512-B5pyYVH93Etv7xjT6IfB7QtMBdaaC07yjbhN6v8H7KgFStMkPvi+oWYBTibMFRMY89qwc9H8YixXg8SXDVgYWw==
dependencies:
"@gql.tada/internal" "^1.0.0"
graphql "^15.5.0 || ^16.0.0 || ^17.0.0"

"@ampproject/remapping@^2.1.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
Expand Down Expand Up @@ -1287,6 +1300,22 @@
unique-filename "^1.1.1"
which "^1.3.1"

"@gql.tada/[email protected]":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@gql.tada/cli-utils/-/cli-utils-1.6.3.tgz#b893cec74908da4df0602691e2e0b1497fda8cda"
integrity sha512-jFFSY8OxYeBxdKi58UzeMXG1tdm4FVjXa8WHIi66Gzu9JWtCE6mqom3a8xkmSw+mVaybFW5EN2WXf1WztJVNyQ==
dependencies:
"@0no-co/graphqlsp" "^1.12.13"
"@gql.tada/internal" "1.0.8"
graphql "^15.5.0 || ^16.0.0 || ^17.0.0"

"@gql.tada/[email protected]", "@gql.tada/internal@^1.0.0":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@gql.tada/internal/-/internal-1.0.8.tgz#3ba9fa6534809788bbbe103492f70b8e9d754027"
integrity sha512-XYdxJhtHC5WtZfdDqtKjcQ4d7R1s0d1rnlSs3OcBEUbYiPoJJfZU7tWsVXuv047Z6msvmr4ompJ7eLSK5Km57g==
dependencies:
"@0no-co/graphql.web" "^1.0.5"

"@humanwhocodes/config-array@^0.11.13", "@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
Expand Down Expand Up @@ -2883,6 +2912,14 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==

"@urql/core@^5.0.8":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@urql/core/-/core-5.1.0.tgz#7f4b81f1aba1ca34ae6354763abeb87ff9af84ff"
integrity sha512-yC3sw8yqjbX45GbXxfiBY8GLYCiyW/hLBbQF9l3TJrv4ro00Y0ChkKaD9I2KntRxAVm9IYBqh0awX8fwWAe/Yw==
dependencies:
"@0no-co/graphql.web" "^1.0.5"
wonka "^6.3.2"

"@zkochan/cmd-shim@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz#2ab8ed81f5bb5452a85f25758eb9b8681982fd2e"
Expand Down Expand Up @@ -3821,6 +3858,17 @@ cashaddrjs-slp@^0.2.11:
dependencies:
big-integer "^1.6.34"

chaingraph-ts@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/chaingraph-ts/-/chaingraph-ts-0.2.3.tgz#0d6a029fe8a0d99934431f4eda18771297825e7e"
integrity sha512-21lJoTaXMrp0pBZX1Cl51TdIUY5OIUZ7dXYRO9jY+Tpgjkc882cQpWMZw+LPqE3sHbgXkMfQEepEwbI7Hay3CA==
dependencies:
"@bitauth/libauth" "^3.0.0"
"@urql/core" "^5.0.8"
gql.tada "^1.8.10"
graphql-ws "^5.16.0"
ws "^8.18.0"

chalk-template@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-1.1.0.tgz#ffc55db6dd745e9394b85327c8ac8466edb7a7b1"
Expand Down Expand Up @@ -6059,6 +6107,16 @@ gopd@^1.0.1, gopd@^1.2.0:
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==

gql.tada@^1.8.10:
version "1.8.10"
resolved "https://registry.yarnpkg.com/gql.tada/-/gql.tada-1.8.10.tgz#096a1b30d3c6fc74212fe07d507f01a4095f7f67"
integrity sha512-FrvSxgz838FYVPgZHGOSgbpOjhR+yq44rCzww3oOPJYi0OvBJjAgCiP6LEokZIYND2fUTXzQAyLgcvgw1yNP5A==
dependencies:
"@0no-co/graphql.web" "^1.0.5"
"@0no-co/graphqlsp" "^1.12.13"
"@gql.tada/cli-utils" "1.6.3"
"@gql.tada/internal" "1.0.8"

graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
Expand All @@ -6079,6 +6137,16 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==

graphql-ws@^5.16.0:
version "5.16.2"
resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.16.2.tgz#7b0306c1bdb0e97a05e800ccd523f46fb212e37c"
integrity sha512-E1uccsZxt/96jH/OwmLPuXMACILs76pKF2i3W861LpKBCYtGIyPQGtWLuBLkND4ox1KHns70e83PS4te50nvPQ==

"graphql@^15.5.0 || ^16.0.0 || ^17.0.0":
version "16.10.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c"
integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==

growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
Expand Down Expand Up @@ -11094,6 +11162,11 @@ windows-release@^3.1.0:
dependencies:
execa "^1.0.0"

wonka@^6.3.2:
version "6.3.4"
resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.3.4.tgz#76eb9316e3d67d7febf4945202b5bdb2db534594"
integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==

word-wrap@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
Expand Down Expand Up @@ -11191,6 +11264,11 @@ ws@^7.5.2:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==

ws@^8.18.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==

xdg-basedir@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9"
Expand Down