Skip to content

Commit

Permalink
STREAM-1091: expose on-chain call utils, fix exports (#128)
Browse files Browse the repository at this point in the history
* STREAM-1091: expose on-chain call utils, fix exports
  • Loading branch information
Yolley authored Jan 23, 2024
1 parent 39fa0c4 commit 8f74eb9
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 55 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ dist/
/local/
.DS_Store
packages/.DS_Store
*.tgz
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"packages": [
"packages/*"
],
"version": "5.9.10",
"version": "5.10.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
4 changes: 4 additions & 0 deletions packages/stream/aptos/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export { default, default as AptosStreamClient } from "./StreamClient";

export * from "./utils";

export * from "./types";

export * as constants from "./constants";
4 changes: 4 additions & 0 deletions packages/stream/evm/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export { default, default as EvmStreamClient } from "./StreamClient";

export * from "./utils";

export * from "./types";

export * as constants from "./constants";
4 changes: 2 additions & 2 deletions packages/stream/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@streamflow/stream",
"version": "5.9.10",
"version": "5.10.0",
"description": "JavaScript SDK to interact with Streamflow protocol.",
"main": "dist/index.js",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "rm -rf dist; tsc -p tsconfig.json",
Expand Down
57 changes: 9 additions & 48 deletions packages/stream/solana/StreamClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import BN from "bn.js";
import { Buffer } from "buffer";
import bs58 from "bs58";
import { ASSOCIATED_TOKEN_PROGRAM_ID, NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import {
Connection,
Expand All @@ -15,8 +14,6 @@ import {
Transaction,
Commitment,
ConnectionConfig,
sendAndConfirmRawTransaction,
BlockheightBasedTransactionConfirmationStrategy,
} from "@solana/web3.js";
import { createAssociatedTokenAccountInstruction } from "@solana/spl-token";
import * as borsh from "borsh";
Expand All @@ -38,9 +35,9 @@ import {
decodeStream,
extractSolanaErrorCode,
getProgramAccounts,
isSignerWallet,
sendAndConfirmStreamRawTransaction,
signAllTransactionWithRecipients,
signAndExecuteTransaction,
} from "./utils";
import {
PROGRAM_ID,
Expand Down Expand Up @@ -232,7 +229,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
tx.partialSign(metadata);
}

const signature = await this.sign(sender, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, sender, tx, hash);

return { ixs, txId: signature, metadataId: metadataPubKey.toBase58() };
}
Expand Down Expand Up @@ -347,7 +344,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
tx.partialSign(metadata);
}

const signature = await this.sign(sender, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, sender, tx, hash);

return { ixs, txId: signature, metadataId: metadataPubKey.toBase58() };
}
Expand Down Expand Up @@ -506,7 +503,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
lastValidBlockHeight: hash.lastValidBlockHeight,
}).add(...ixs);

const signature = await this.sign(invoker, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, invoker, tx, hash);

return { ixs, txId: signature };
}
Expand Down Expand Up @@ -564,7 +561,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
lastValidBlockHeight: hash.lastValidBlockHeight,
}).add(...ixs);

const signature = await this.sign(invoker, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, invoker, tx, hash);

return { ixs, txId: signature };
}
Expand Down Expand Up @@ -617,7 +614,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
lastValidBlockHeight: hash.lastValidBlockHeight,
}).add(...ixs);

const signature = await this.sign(invoker, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, invoker, tx, hash);

return { ixs, txId: signature };
}
Expand Down Expand Up @@ -674,7 +671,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
lastValidBlockHeight: hash.lastValidBlockHeight,
}).add(...nativeInstructions, ...ixs);

const signature = await this.sign(invoker, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, invoker, tx, hash);

return { ixs, txId: signature };
}
Expand Down Expand Up @@ -739,40 +736,6 @@ export default class SolanaStreamClient extends BaseStreamClient {
return sortedStreams.filter((stream) => stream[1].type === type);
}

private async sign(
invoker: any,
tx: Transaction,
hash: Readonly<{
blockhash: string;
lastValidBlockHeight: number;
}>
) {
let signedTx: Transaction;
if (isSignerWallet(invoker)) {
signedTx = await invoker.signTransaction(tx);
} else {
tx.partialSign(invoker);
signedTx = tx;
}

const rawTx = signedTx.serialize();

if (!hash.lastValidBlockHeight || !signedTx.signature || !hash.blockhash)
throw Error("Error with transaction parameters.");

const confirmationStrategy: BlockheightBasedTransactionConfirmationStrategy = {
lastValidBlockHeight: hash.lastValidBlockHeight,
signature: bs58.encode(signedTx.signature),
blockhash: hash.blockhash,
};
const signature = await sendAndConfirmRawTransaction(
this.connection,
rawTx,
confirmationStrategy
);
return signature;
}

/**
* Attempts updating the stream auto withdrawal params and amount per period
*/
Expand Down Expand Up @@ -807,7 +770,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
lastValidBlockHeight: hash.lastValidBlockHeight,
}).add(updateIx);

const signature = await this.sign(invoker, tx, hash);
const signature = await signAndExecuteTransaction(this.connection, invoker, tx, hash);

return {
ixs: [updateIx],
Expand Down Expand Up @@ -981,9 +944,7 @@ export default class SolanaStreamClient extends BaseStreamClient {
invoker.publicKey!,
accountArrays[i][1],
accountArrays[i][0],
data.mint,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
data.mint
)
);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/stream/solana/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,9 @@ export interface BatchItemError extends BatchItem {
error: string;
}

export interface AtaParams {
mint: PublicKey;
owner: PublicKey;
}

export type BatchItemResult = BatchItemSuccess | BatchItemError;
125 changes: 121 additions & 4 deletions packages/stream/solana/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { getAssociatedTokenAddress } from "@solana/spl-token";
import {
createAssociatedTokenAccountInstruction,
getAssociatedTokenAddress,
} from "@solana/spl-token";
import { SignerWalletAdapter } from "@solana/wallet-adapter-base";
import {
BlockheightBasedTransactionConfirmationStrategy,
Connection,
Keypair,
PublicKey,
TransactionInstruction,
Transaction,
sendAndConfirmRawTransaction,
BlockhashWithExpiryBlockHeight,
} from "@solana/web3.js";
import BN from "bn.js";
import bs58 from "bs58";

import { streamLayout } from "./layout";
import { DecodedStream, Account, BatchItem, BatchItemResult } from "./types";
import { AtaParams, DecodedStream, Account, BatchItem, BatchItemResult } from "./types";
import { SOLANA_ERROR_MAP, SOLANA_ERROR_MATCH_REGEX } from "./constants";

const decoder = new TextDecoder("utf-8");
Expand Down Expand Up @@ -97,7 +103,9 @@ export async function getProgramAccounts(
* @param {Keypair | SignerWalletAdapter} walletOrKeypair - Wallet or Keypair in question
* @return {boolean} - Returns true if parameter is a Wallet.
*/
export function isSignerWallet(walletOrKeypair: Keypair | SignerWalletAdapter): boolean {
export function isSignerWallet(
walletOrKeypair: Keypair | SignerWalletAdapter
): walletOrKeypair is SignerWalletAdapter {
return (<SignerWalletAdapter>walletOrKeypair).signTransaction !== undefined;
}

Expand All @@ -106,7 +114,7 @@ export function isSignerWallet(walletOrKeypair: Keypair | SignerWalletAdapter):
* @param walletOrKeypair {Keypair | SignerWalletAdapter} walletOrKeypair - Wallet or Keypair in question
* @returns {boolean} - Returns true if parameter is a Keypair.
*/
function isSignerKeypair(
export function isSignerKeypair(
walletOrKeypair: Keypair | SignerWalletAdapter
): walletOrKeypair is Keypair {
return (
Expand All @@ -116,6 +124,20 @@ function isSignerKeypair(
);
}

export async function signTransaction(
invoker: Keypair | SignerWalletAdapter,
tx: Transaction
): Promise<Transaction> {
let signedTx: Transaction;
if (isSignerWallet(invoker)) {
signedTx = await invoker.signTransaction(tx);
} else {
tx.partialSign(invoker);
signedTx = tx;
}
return signedTx;
}

/**
* Sign passed BatchItems with wallet request or KeyPair
* @param {Keypair | SignerWalletAdapter} sender - Wallet or Keypair of sendin account
Expand Down Expand Up @@ -146,6 +168,35 @@ export async function signAllTransactionWithRecipients(
}
}

/**
* Signs, sends and confirms Transaction
* @param connection - Solana client connection
* @param invoker - Keypair used as signer
* @param tx - Transaction instance
* @param hash - blockhash information, the same hash should be used in the Transaction
* @returns Transaction signature
*/
export async function signAndExecuteTransaction(
connection: Connection,
invoker: Keypair | SignerWalletAdapter,
tx: Transaction,
hash: BlockhashWithExpiryBlockHeight
): Promise<string> {
const signedTx = await signTransaction(invoker, tx);
const rawTx = signedTx.serialize();

if (!hash.lastValidBlockHeight || !signedTx.signature || !hash.blockhash)
throw Error("Error with transaction parameters.");

const confirmationStrategy: BlockheightBasedTransactionConfirmationStrategy = {
lastValidBlockHeight: hash.lastValidBlockHeight,
signature: bs58.encode(signedTx.signature),
blockhash: hash.blockhash,
};
const signature = await sendAndConfirmRawTransaction(connection, rawTx, confirmationStrategy);
return signature;
}

/**
* Sign passed BatchItems with wallet request or KeyPair
* @param {Connection} connection - Solana web3 connection object.
Expand Down Expand Up @@ -191,6 +242,72 @@ export function ata(mint: PublicKey, owner: PublicKey): Promise<PublicKey> {
return getAssociatedTokenAddress(mint, owner, true);
}

/**
* Function that checks whether ATA exists for each provided owner
* @param connection - Solana client connection
* @param paramsBatch - Array of Params for an each ATA account: {mint, owner}
* @returns Array of boolean where each members corresponds to owners member
*/
export async function ataBatchExist(
connection: Connection,
paramsBatch: AtaParams[]
): Promise<boolean[]> {
const tokenAccounts = await Promise.all(
paramsBatch.map(async ({ mint, owner }) => {
const pubkey = await ata(mint, owner);
return pubkey;
})
);
const response = await connection.getMultipleAccountsInfo(tokenAccounts);
return response.map((accInfo) => !!accInfo);
}

/**
* Generates a Transaction to create ATA for an array of owners
* @param connection - Solana client connection
* @param payer - Transaction invoker, should be a signer
* @param coparamsBatchnfigs - Array of Params for an each ATA account: {mint, owner}
* @returns Unsigned Transaction with create ATA instructions
*/
export async function generateCreateAtaBatchTx(
connection: Connection,
payer: PublicKey,
paramsBatch: AtaParams[]
): Promise<{
tx: Transaction;
hash: BlockhashWithExpiryBlockHeight;
}> {
const ixs: TransactionInstruction[] = await Promise.all(
paramsBatch.map(async ({ mint, owner }) => {
return createAssociatedTokenAccountInstruction(payer, await ata(mint, owner), owner, mint);
})
);
const hash = await connection.getLatestBlockhash();
const tx = new Transaction({
feePayer: payer,
blockhash: hash.blockhash,
lastValidBlockHeight: hash.lastValidBlockHeight,
}).add(...ixs);
return { tx, hash };
}

/**
* Creates ATA for an array of owners
* @param connection - Solana client connection
* @param invoker - Transaction invoker and payer
* @param paramsBatch - Array of Params for an each ATA account: {mint, owner}
* @returns Transaction signature
*/
export async function createAtaBatch(
connection: Connection,
invoker: Keypair | SignerWalletAdapter,
paramsBatch: AtaParams[]
): Promise<string> {
const { tx, hash } = await generateCreateAtaBatchTx(connection, invoker.publicKey!, paramsBatch);
const signature = await signAndExecuteTransaction(connection, invoker, tx, hash);
return signature;
}

export function extractSolanaErrorCode(errorText: string): string | null {
const match = SOLANA_ERROR_MATCH_REGEX.exec(errorText);

Expand Down
4 changes: 4 additions & 0 deletions packages/stream/sui/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export { default as SuiStreamClient } from "./StreamClient";

export * from "./utils";

export * from "./types";

export * as constants from "./constants";

0 comments on commit 8f74eb9

Please sign in to comment.