Skip to main content

Adding data

You may want to insert data from another blockchain and store it through a KYVE data pool onto decentralized storage providers such as Arweave.

To do this, you'll want to write an integration.

0. Gather necessary information from the chain you would like to integrate into KYVE

  • RPC endpoint
  • SDK's for the source blockchain

1. Clone the template repo

git clone https://github.com/yirenlu92/KyveIntegrationTemplate
cd KyveIntegrationTemplate

2. Add configuration for your chain

Open up package.json in your cloned repo.

vi package.json

Update the name and version to fit your integration/runtime.

So for example:

{
"name": "@kyve/solana",
"version": "0.1.0",
...
}

3. Extend the KYVE class

Now open up the src/index.ts file from the root of the repo.

vi src/index.ts

All the places that you need to change will be marked with a TODO: comment -- make sure you follow the instructions below to fill them all in.

Extend the KYVE class with a camel-cased class extension name, for example:

import

class KyveSolana extends KYVE {
...
}

4. Fill in the getDataItem method

The getDataItem method that you implement should take in a key, which is the height of the blockchain block you are indexing. (Block height refers to a specific location in a blockchain, measured by how many confirmed blocks precede it.) The method should return the value for that particular key, which can be anything from a JSON object to a string.

  public async getDataItem(key: number): Promise<{ key: number; value: any }>

Every getDataItem method will likely take the RPC endpoint from step 1, and either pass it to an SDK provided by the source chain or third party library provider like Ethers or Web3JS; OR the method will call the public RPC endpoint directly for data.

Once the data comes back from the source chain, you can choose to clean up the data before returning it in the method.

Examples

KYVE-Solana Integration

As an example, let's walk through the Solana integration. The KyveSolana class is as below.

class KyveSolana extends KYVE {
public async getDataItem(key: number): Promise<{ key: number; value: any }> {
let block;

try {
block = await fetchBlock(
this.pool.config.rpc,
key,
await this.getSignature()
);
} catch (err) {
if (wasSlotSkipped(err, key)) return { key, value: null };

this.logger.warn(
`⚠️ EXTERNAL ERROR: Failed to fetch block ${key}. Retrying ...`
);

throw err;
}

return { key, value: block };
}

private async getSignature(): Promise<Signature> {
// ...
}
}

Let's take a closer look at the fetchBlock helper method, which in this case lives in its own utils.ts file.

import { BlockResponse, Connection } from "@solana/web3.js";

// NOTE: The response type isn't correct because of our patch.
export async function fetchBlock(
endpoint: string,
height: number,
signature: Signature
): Promise<BlockResponse> {
const provider = initialiseSolanaRPC(endpoint, signature);

return (await provider.getBlock(height))!;
}

function initialiseSolanaRPC(
endpoint: string,
signature: Signature
): Connection {
return new Connection(endpoint, {
httpHeaders: {
"Content-Type": "application/json",
Signature: signature.signature,
"Public-Key": signature.pubKey,
"Pool-ID": signature.poolId,
Timestamp: signature.timestamp,
},
});
}

The fetchBlock method first initializes a connection to the Solana API and then calls the getBlock() method that is provided in the Solana SDK.

KYVE-Cosmos Integration

The fetchBlock helper method for the KYVE-Cosmos integration fetches data from the source chain (Cosmos) directly from Cosmos's RPC endpoints.

export async function fetchBlock(
endpoint: string,
height: number,
signature: Signature
): Promise<any> {
const res = await call<any>(
`${endpoint}/cosmos/base/tendermint/v1beta1/blocks/${height}`,
signature
);

const txs = await fetchTransactions(
endpoint,
res.block.data.txs.map(parseEncodedTx),
signature
);

return {
...res.block,
data: {
...res.block.data,
txs,
},
};
}

Custom Logic

In addition to the getDataItem method which must be implemented, you are free to implement custom methods in your class.

Custom Signatures

For example, we have implemented custom signatures for several of the existing integrations.

These signatures, which were implemented to prevent spamming of private endpoints, are calculated from the address (conveniently exposed in the class through your wallet) and the message. They can be used for signature verification.

class KyveSolana extends KYVE {
public async getDataItem(key: number): Promise<{ key: number; value: any }> {
let block;

try {
block = await fetchBlock(
this.pool.config.rpc,
key,
await this.getSignature()
);
} catch (err) {
...
}

return { key, value: block };
}

private async getSignature(): Promise<Signature> {
const address = await this.sdk.wallet.getAddress();
const timestamp = new Date().valueOf().toString();

const message = `${address}//${this.poolId}//${timestamp}`;

const { signature, pub_key } = await this.sdk.signString(message);

return {
signature,
pubKey: pub_key.value,
poolId: this.poolId.toString(),
timestamp,
};
}
}

5. Test your integration

Now that you've successfully extended KYVE and implemented your getDataItem class method, it's time to test your integration!

To proceed to this next step, reach out to the KYVE Discord channel for help.