#!/usr/bin/env node /* Copyright (c) 2021 Ivan Jelincic * * This file is part of streamflow-program * https://github.com/StreamFlow-Finance/streamflow-program * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // This file serves as a reference on how to use streamflow-program // with Javascript. const BufferLayout = require("buffer-layout"); const sol = require("@solana/web3.js"); const fs = require('fs'); // Cluster and program address to use // const cluster = "http://localhost:8899"; const cluster = sol.clusterApiUrl("devnet", true); const programAddr = "2DvvSEde36Ch3B52g9hKWDYbfmJimLpJwVBV9Cknypi4" // Alice is our sender, make sure there is funds in the account. // 71G4rRM4DugVRmAwEUtBNaw8xwGKZmSujwjFy37ErphW let alice; if (process.env.ALICE !== undefined) { alice = sol.Keypair.fromSecretKey( Buffer.from(JSON.parse(fs.readFileSync(process.env.ALICE, "utf-8")))); } else { alice = sol.Keypair.fromSecretKey(Buffer.from([97, 93, 122, 16, 225, 220, 239, 230, 206, 134, 241, 223, 228, 135, 202, 29, 7, 124, 108, 250, 96, 12, 103, 91, 103, 95, 201, 25, 156, 18, 98, 149, 89, 55, 40, 62, 196, 151, 180, 107, 249, 9, 23, 53, 215, 63, 170, 57, 173, 9, 36, 82, 233, 112, 55, 16, 15, 247, 47, 250, 115, 98, 210, 129])); } // await connection.requestAirdrop(alice.publicKey, 1000000000); // Bob is our recipient // H4wPUkepkJgB2FMaRyZWvsSpNUK8exoMonbRgRsipisb let bob; if (process.env.BOB !== undefined) { bob = sol.Keypair.fromSecretKey( Buffer.from(JSON.parse(fs.readFileSync(process.env.BOB, "utf-8")))); } else { bob = sol.Keypair.fromSecretKey(Buffer.from([104, 59, 250, 44, 167, 108, 233, 202, 30, 232, 3, 91, 108, 141, 125, 241, 216, 86, 189, 157, 48, 69, 78, 98, 125, 6, 150, 127, 41, 214, 124, 242, 238, 189, 58, 189, 215, 194, 98, 74, 98, 184, 196, 38, 158, 174, 51, 135, 76, 147, 74, 61, 214, 178, 94, 233, 190, 216, 78, 115, 83, 39, 99, 226])); } function usage() { console.log("usage: strfi.js [init|withdraw|cancel] [accountAddress (needed for withdraw/cancel)]"); process.exit(1); } // This is the structure for the init instruction const initLayout = BufferLayout.struct([ BufferLayout.u8("instruction"), BufferLayout.u32("starttime"), BufferLayout.u32("endtime"), // N.B. Use something else, this goes up to 2^53 BufferLayout.nu64("amount"), ]); // This is the structure for the withdraw instruction const withdrawLayout = BufferLayout.struct([ BufferLayout.u8("instruction"), // N.B. Use something else, this goes up to 2^53 BufferLayout.nu64("amount"), ]); // This is the structure for the cancel instruction const cancelLayout = BufferLayout.struct([ BufferLayout.u8("instruction"), ]); async function initStream(connection) { // Current time as Unix timestamp now = Math.floor(new Date().getTime() / 1000); var data = Buffer.alloc(initLayout.span); initLayout.encode({ // 0 means init in the Rust program. instruction: 0, // Unix timestamp when the stream should start unlocking. starttime: now + 10, // Unix timestamp when the stream should finish and unlock everything. endtime: now + 610, // Lamports to stream amount: 100000000, }, data, ); // pda is a new keypair where the funds are sent, and program metadata // is kept and updated by the program. const pda = new sol.Keypair(); console.log("ALICE: %s", alice.publicKey.toBase58()); console.log("BOB: %s", bob.publicKey.toBase58()); console.log("PDA: %s", pda.publicKey.toBase58()); console.log("DATA:", data); const instruction = new sol.TransactionInstruction({ keys: [{ // Alice is the stream sender. pubkey: alice.publicKey, isSigner: true, isWritable: true, }, { // Bob is the stream recipient. pubkey: bob.publicKey, isSigner: false, isWritable: true, }, { // pda is the account that will be created. // It shall contain the locked funds and necessary metadata. pubkey: pda.publicKey, isSigner: true, isWritable: true, }, { // This is the system program public key. pubkey: sol.SystemProgram.programId, isSigner: false, isWritable: false, }], programId: new sol.PublicKey(programAddr), data: data, }); // Transaction signed by Alice and the new pda. tx = new sol.Transaction().add(instruction); return await sol.sendAndConfirmTransaction(connection, tx, [alice, pda]); } async function withdrawStream(connection, accountAddr) { var data = Buffer.alloc(withdrawLayout.span); withdrawLayout.encode({ // 1 means withdraw in the Rust program. instruction: 1, // When amount is 0 lamports, then withdraw everything // that is unlocked on the stream. Otherwise, arbitrary // values are allowed. amount: 0, }, data, ); console.log("ALICE: %s", alice.publicKey.toBase58()); console.log("BOB: %s", bob.publicKey.toBase58()); console.log("PDA: %s", accountAddr); console.log("DATA:", data); const instruction = new sol.TransactionInstruction({ keys: [{ // Bob is the stream recipient. pubkey: bob.publicKey, isSigner: true, isWritable: true, }, { // This is the public key of the account where the funds // and metadata are held. pubkey: new sol.PublicKey(accountAddr), isSigner: false, isWritable: true, }, { // This address is hardcoded in the program, and is supposed // to collect the remaining rent when everything is withdrawn // from the stream successfully. pubkey: new sol.PublicKey("DrFtxPb9F6SxpHHHFiEtSNXE3SZCUNLXMaHS6r8pkoz2"), isSigner: false, isWritable: true, }, { // This is the system program public key. pubkey: sol.SystemProgram.programId, isSigner: false, isWritable: false, }], programId: new sol.PublicKey(programAddr), data: data, }); // Transaction signed by Bob. tx = new sol.Transaction().add(instruction); return await sol.sendAndConfirmTransaction(connection, tx, [bob]); } async function cancelStream(connection, accountAddr) { var data = Buffer.alloc(cancelLayout.span); cancelLayout.encode({ // 2 means cancel in the Rust program. instruction: 2, }, data, ); console.log("ALICE: %s", alice.publicKey.toBase58()); console.log("BOB: %s", bob.publicKey.toBase58()); console.log("PDA: %s", accountAddr); console.log("DATA:", data); // The transaction instruction contains the public keys used. const instruction = new sol.TransactionInstruction({ keys: [{ // Alice is our initial stream sender. pubkey: alice.publicKey, isSigner: true, isWritable: true, }, { // Bob is the stream recipient. pubkey: bob.publicKey, isSigner: false, isWritable: true, }, { // This is the public key of the account where the funds // and metadata are held. pubkey: new sol.PublicKey(accountAddr), isSigner: false, isWritable: true, }, { // This is the system program public key. pubkey: sol.SystemProgram.programId, isSigner: false, isWritable: false, }], programId: new sol.PublicKey(programAddr), data: data, }); // Transaction signed by Alice. tx = new sol.Transaction().add(instruction); return await sol.sendAndConfirmTransaction(connection, tx, [alice]); } async function main(args) { if (process.argv.length < 3 || process.argv.length > 4) { usage(); } const conn = new sol.Connection(cluster); switch (args[2]) { case "init": console.log("TXID:", await initStream(conn)); break; case "withdraw": if (args.length != 4) { console.log("Missing metadata/funds account address"); usage(); } console.log("TXID:", await withdrawStream(conn, args[3])); break; case "cancel": if (args.length != 4) { console.log("Missing metadata/funds account address"); usage(); } console.log("TXID:", await cancelStream(conn, args[3])); break; default: usage(); } } main(process.argv).then(() => process.exit(0)).catch(e => console.error(e));