Git Product home page Git Product logo

algonautjs's Introduction

Algonaut.js

Examples | API Docs

Algonaut.js is designed to be a front end developer friendly and browser friendly library to facilitate intuitive transactions on the Algorand network. Algorand is incredibly fast, powerful and secure, but it's early days and the tooling is still a little intense.

After working with the existing frameworks for a couple months, we decided to just create what we felt was missing! Get in here and help!

THE GOAL OF THIS LIBRARY IS SIMPLICITY AND EASE OF USE for the front end crew. This library aims to bring you closer to key concepts behind the mature transactional fiesta that is Algorand though ease of use. If you need a robust contract creation and debugging environment, please take the wonderful Algo Builder for a spin!

We package, expose and depend on the JavaScript Algosdk. It's there if you need it, but that API is pretty intense for day-to-day use. With Algonaut.js, you can run one-off transactions with a vastly simplified API as compared to transacting with the Algosdk directly. We're trying to solve for the 90% cases and ask you to dive into the hard stuff only if you actually need to use it.

Usage

Install:

# npm
npm install @thencc/algonautjs
# or, pnpm
pnpm add @thencc/algonautjs
# or, install the beta release
pnpm add @thencc/algonautjs@beta

Basic usage:

// 1. import lib
import { Algonaut } from '@thencc/algonautjs';

// 2. create lib instance
const algonaut = new Algonaut(); // uses algonaut testnet by default

// 3. authenticate (ex: using inkey microwallet)
const accounts = await algonaut.connect('inkey');

// 4. contruct a txn + submit it to the network (uses suggested network params)
const txnStatus = await algonaut.sendAlgo({
  to: 'DXS4S2WZMFAFA2WDSIRNCZGVSQQ72XEKVED63NA7KWVLKWBWZ6AABQYMTY',
  amount: 1000, // micro algos
  optionalFields: { note: 'a note for the transaction' }
});
console.log(txnStatus);

Authenticating / Connecting a Wallet

Wallets

algonaut supports most all Algorand wallets such as...

  • ✅ Pera
  • ✅ Inkey
  • ✅ MyAlgo
  • ✅ AlgoSigner
  • ✅ Exodus
  • ✅ Defly
  • ✅ Mnemonic (not secure client-side)

...by using any-wallet under the hood.

any-wallet uses localstorage to remember which wallet(s) a user has connected to the dapp previously and the entire any-wallet reactive state is made available at algonaut.walletState for convenience. see the any-wallet documentation for more info.

Accounts

algonaut thinks about accounts like this:

  • algonaut.account
    • is the active account from which txns are signed + sent
    • only 1 account is active at a time
    • it can be from any wallet provider (Pera, Inkey, etc.)
    • when txn signing is required the wallet ui corresponding to the account's walletId will show
    • it serves as the default .from field in new txn contructions (unless otherwise specified)
  • algonaut.connectedAccounts
    • are any number of accounts that a user has connected to this dapp
    • 1 of these is the active account

the structure of an account object looks like this:

{
  address: 'DXS4S2WZMFAFA2WDSIRNCZGVSQQ72XEKVED63NA7KWVLKWBWZ6AABQYMTY',
  name: 'silvio',
  walletId: 'inkey',
  chain: 'algorand',
  active: true,
  dateConnected: 1687889579236, // unix timestamp as number
  dateLastActive: 1687889579236 || undefined //
}

by default the most recently connected account becomes the active account. however, you can update the active account to one of the connected accounts like so:

algonaut.setActiveAccount(algonaut.connectedAccounts[2]);

🔌 ex: simple connect ↕

At it's simplest, algonaut can connect a user's wallet by awaiting the .connect() method, which takes an object with exactly ONE entry where the WALLET_ID of the desired provider is the key and the value is true. this loads the wallet's sdk on demand and prompts the user to signin to it. for example:

// inkey microwallet
let accts = await algonaut.connect('inkey');

// or, pera wallet
let accts = await algonaut.connect('pera');

// now algonaut uses the first returned acct as the active account
algonaut.account == accts[0];

🧮 ex: complex connect ↕

import { 
  Algonaut,
  WALLET_ID,
} from '@thencc/algonautjs';

// set wallet init params for each wallet
const algonaut = new Algonaut({
  initWallets: {
    [WALLET_ID.INKEY]: {
      config: {
        align: 'right', // use custom config or true for defaults
      }
    },
    [WALLET_ID.PERA]: true,
    [WALLET_ID.MYALGO]: true,
    [WALLET_ID.ALGOSIGNER]: true,
    [WALLET_ID.EXODUS]: true,
    [WALLET_ID.DEFLY]: true,
    [WALLET_ID.MNEMONIC]: '25 word phrase',
  }
});

// connect w init params set on initialization
let accts = await algonaut.connect('inkey');

// or, connect w new init params as 2nd arg (FYI this is all typed)
let accts = algonaut.connect('inkey', {
  config: {
    src: 'http://localhost:5200/'
  }
});

📝 ex: mnemonic wallet ↕

note: NOT SECURE for client-side use but can be useful for rapid local development.

// connect
const algonaut = new Algonaut();
let accts = await algonaut.connect('mnemonic', '25 word phrase');

// FYI no wallet ui is shown for mnemonic wallet signing
// so the below simply works without any user interaction:
const res = algonaut.sendAlgo({
  amount: 100, // micro algos
  to: 'DXS4S2WZMFAFA2WDSIRNCZGVSQQ72XEKVED63NA7KWVLKWBWZ6AABQYMTY', // recipient
});

🐙 ex: advance inkey ↕

algonaut is optimized for inkey! on the top level of algonaut there are useful methods such as:

  • algonaut.inkeyLoaded
  • algonaut.inkeyLoading
  • algonaut.inkeyShow()
  • algonaut.inkeyHide()
  • algonaut.getInkeyClientSdk()
    • which returns the inkey-client-js instance

👀 ex: watch account changes ↕

You can subscribe to account changes like so:

import { 
  Algonaut
} from '@thencc/algonautjs';

const algonaut = new Algonaut();

const unsubscribe = algonaut.subscribeToAccountChanges(
  (acct) => {
    if (acct) {
      // authenticated
      console.log(acct.address);
    } else {
      // un-authenticated
    }
  }
);

// + can unsubscribe
unsubscribe();

🔋 ex: recall state / reconnect ↕

if your dapp wants to recall previously connected accounts from localstorage, use the same storageKey during intialization

import { 
  Algonaut
} from '@thencc/algonautjs';

const algonautA = new Algonaut({
  storageKey: 'state1'
});
const algonautB = new Algonaut({
  storageKey: 'state1'
});

// now, algonautA.activeAddress == algonautB.activeAddress

// and if a user connected accounts during a previous session, the active account + connected accounts are now populated
console.log(algonautA.connectedAccounts);
console.log(algonautA.account);

Submitting Transactions

Submitting/sending transactions is common practice on a dapp and algonautjs makes it simple! algonaut.sendTransaction() signs and submits the incoming single txn or array of txns (atomic txn).

Important Algorand knowledge:

Before a txn is submitted, it first needs to be signed which algonaut handles gracefully. When algonaut has an active account and .sendTransaction() is called, it prompts the user to sign the transaction in whichever wallet UI the account was connected with. After the user approves/signs, the signed transaction is returned to the dapp and submitted to the network. While submitting is nearly instant, awaiting the send method yields the confirmed block/round number in which the txn now exists on-chain (this takes ~3.5 seconds). If a dapp needs more granularity re signing, sending and confirming a txn it can leverage callback hooks as a second arguement (see the atomic txn example below).

🚀 single txn send ↕

// construct txn
const txn = await algonaut.atomicSendAlgo({
  amount: 1000, // micro-algos
  to: receiverAddr,
  from: senderAddr // .from needed IF algonaut isnt authenticated and doesnt have this.account populated
});
console.log('txn', txn);

// sign + submit txn
let txnRes = await algonaut.sendTransaction(txn);
console.log('txnRes', txnRes);

🛸 atomic txn example ↕

A powerful feature of the Algorand chain is the ability to group transactions together and run them as one atomic transactions.

// the logic in the 2nd txn's smart contract requires that the first txn in this atomic transaction is a payment txn to a specific address in order for the second txn to succeed.
// - to interact w app/smart contracts you must include the app id in the optionalFields.applications array.
// - similarly, to interact w any asset, the asset id must by included in the optionalFields.assets array.
const txnStatus = await algonaut.sendTransaction([
  await algonaut.atomicSendAlgo({ to: appAddress, amount: 250000 }),
  await algonaut.atomicCallApp({
    appIndex: appIndex,
    appArgs: ['get_bananas'],
    optionalFields: { applications: [ bananaPriceTicker ] , assets: [ bananaAsaIndex ]
  })
]);
// .sendTransaction will return the await once the txn is confirmed (successfully committed to algo chain state)

// CALLBACKS: you can also get more specific callbacks by passing a 2nd arg to .sendTxn() like so:
algonaut.sendTransaction( txnArr , {
  onSign: (e) => {
    //
  },
  onSend: (e) => {
    //
  },
  onConfirm: (e) => {
    //
  }
})

Sign Transactions (without submitting)

// make some txn(s)
const txn1 = await algonaut.atomicSendAlgo({
  amount: 1000,
  to: 'ADWTH6AP6EVAS3PD4JZCYRG26ZLZLC5CSK5QIXD4OHPDKVZE5AEDOIKBBU'
});
const txn2 = await algonaut.atomicOptInAsset(10458941);

// prompts user for txn approval in wallet ui
const signedTxns = await algonaut.signTransactions([
  txn1,
  txn2,
]);
// NOTE: signedTxns is an array of Uint8Array's (raw algo txn bytes)

// you can then submit these raw txns like so:
const txnGroup = await algonaut.algodClient.sendRawTransaction(signedTxns).do();
console.log('tx id: ', txnGroup.txId);

Set Algorand Node

algonaut ships w a default testnet node pre-configured and enabled but feel free to change it. here's how:

// testnet
const algonaut = new Algonaut();
algonaut.setNodeConfig('testnet');

// mainnet
const algonaut = new Algonaut();
algonaut.setNodeConfig('mainnet');

// custom node (on init)
const algonaut = new Algonaut({
  nodeConfig: {
    BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2',
    // key is header, value is token
    API_TOKEN: { 'X-API-Key': 'INSERT_KEY_HERE' }, 
    LEDGER: 'TestNet',
    PORT: ''
  }
});

// custom node (after init)
algonaut.setNodeConfig({
  BASE_SERVER: 'https://mainnet-algorand.api.purestake.io/ps2',
  API_TOKEN: { 'X-API-Key': 'INSERT_KEY_HERE' },
  LEDGER: 'MainNet',
  PORT: ''
});

Deploying Smart Contracts

In case you want to, Algonaut can deploy a smart contract to the current network using TEAL approval + clear code. Use the createApp method to do this like so:

const createAppArgs = {
  tealApprovalCode: `#pragma 5 ...`,
  tealClearCode: `...`,
  appArgs: [],
  schema: {
    localInts: 4,
    localBytes: 12,
    globalInts: 1, // numbers
    globalBytes: 1, // strings
  }
};

const res = await algonaut.createApp(createAppArgs);
const appId = res.createdIndex; // now you can lookup this appId on any algo chain explorer (be sure to look on the matching net, testnet or mainnet)

Interacting with Smart Contracts

Algorand smart contracts are fast, light, and super clean. Trying to communicate their APIs across our team has been really hard. For that reason, Algonaut.js supports a simple TypeScript descriptor for both Stateful and Stateless Smart contracts. The goal is to be able to load this TypeScript descriptor into your dev envirnment and get a sense of what you can and can't do with a contract's API.

Even the concept of Stateless contracts will be a curve climb for a lot of front end people. Our goal with this descriptor approach is to communicate a baseline of information about what transactions are permitted, expected, etc, without the front-end developer needing to go into the TEAL or PyTeal directly to figure this stuff out.

Here again we are trying to account for the 90% use case, not every possible case. The goal is simplicity and ease of use, understanding that there will always be those complex cases where you have to go down to the metal.

FYI: the first app arg is often the method name, or required to be the method selector when interfacing w an ABI compliant smart contract.

const response = await algonaut.callApp({
  appIndex: 123456789,
  appArgs: ['set_name', 'New Name']
});

Testing

Unit tests are in tests/algonaut.test.ts and implemented with Jest.

  1. Copy the .env.sample file and replace the values with your node configuration, and a test account mnemonic that is funded with ALGO.
  2. Run npm run test

Integration tests are also available. Please make sure all tests pass before submitting a pull request! See ./tests/README.md for more details.

Contributing

To generate docs:

npm run docs

Typedoc options are set in typedoc.json.


Publishing to NPM:

[ latest/release ] stable releases have the default npm tag of latest (installable via npm i @thencc/algonautjs or npm i @thencc/algonautjs@latest) and are automatically published from the repo's release branch via a Github Action. so simply pull request the main branch (or feature specific branch) into release to publish to npm.

[ beta/main ] similarly, pushing commits or merging pull requests to the repo's main branch will automatically publish to the npm beta release installable via npm i @thencc/algonautjs@beta.

note: to update either the latest or beta releases, the version in package.json must be higher than the previous release.

VSCode DX Setup

Use extensions:

  • Volar Vue
  • ESLint

TBD:

  • setting up a dev env
  • ESLint and code style
  • building and testing with Vite and Node

algonautjs's People

Contributors

enceladus avatar spencercap avatar youraerials avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

mrchipdev

algonautjs's Issues

make a .defaultFromAddr field?

when we switch over to externalizing the signing providers, this.account doesnt make much sense. the only real use is for populating .from fields w a default addr and the transactionSigner field in atomic txns.

might we introduce a this.setDefaultFromAddr method + associated field so that other (+ unsupported) wallets can use algonaut w ease?

Unit test coverage

I started writing some Jest tests (eea446c). Tracking coverage here.

algonaut core offline methods

  • decodeBase64UnsignedTransaction
  • new Algonaut()
  • isValidConfig
  • getConfig
  • setConfig
  • createWallet
  • recoverAccount
  • decodeStateArray - via getAppInfo
  • encodeArguments - implicit
  • b64StrToHumanStr
  • stateArrayToObject
  • setAccount
  • toUint8Array
  • txnSummary
  • getAppEscrowAccount
  • valueAsAddr

algonaut core online methods

  • normalizeTxns - with more than 1 txn
  • sendTransaction with callbacks
  • compileProgram
  • atomicCallApp
  • callApp
  • atomicCloseOutApp
  • closeOutApp
  • atomicCreateApp
  • createApp
  • atomicCreateAsset
  • createAsset
  • atomicDeleteApplication
  • deleteApplication
  • atomicDeleteAsset
  • deleteAsset
  • atomicOptInApp
  • atomicOptInAsset
  • atomicPayment
  • atomicSendAsset
  • atomicUpdateApp
  • checkStatus
  • getAccountInfo
  • getAlgoBalance
  • getAppGlobalState
  • getAppInfo
  • getAppLocalState
  • getAssetInfo
  • getTokenBalance
  • isOptedIntoAsset
  • optInApp
  • optInAsset
  • updateApp
  • waitForConfirmation (implicit)
  • sendAlgo
  • sendAsset
  • sendTransaction (implicit)

inkey methods

  • inkeyConnect
  • inkeyDisconnect
  • inkeyHide
  • inkeyShow

any-wallet new stuff

  • connect
  • disconnect
  • disconnectAll
  • reconnect

logic sig

  • atomicAssetTransferWithLSig
  • atomicCallAppWithLSig
  • atomicPaymentWithLSig
  • deployTealWithLSig
  • generateLogicSig

multi sig

  • use multiSigMetadata arg + test threshold signing etc

Remove sendTransaction logs

We don't need the "Transaction ID: ..." and "Transaction confirmed in round ..." logs anymore -- this information is contained in the response object anyway.

the big clean up refactor!

lets...

  • modularize functions / lib abilities into individual categorized files / folders
  • pair down redundant/duplicate methods (possibly: sendAtomicTransaction and sendTransaction, and check various account info gets)
  • make private + public class methods
  • optimize for built lib size (treeshake + bundle correctly in esbuild)
  • dont include all of algosdk as .sdk ? @youraerials thoughts?
  • make signing methods Providers (inkey, pera, algosigner, local, etc) see notes here
    • by modularizing wallet connect we can remove the mp3's as .ts files shim too + the request animation frame, silent audio hack for keeping the web socket open while backrounded

Delwin's additions:
These issues should be closed with this refactor, as they significantly impact the structure or default behavior of Algonaut:

And lastly:

  • Update & expand ALL documentation accordingly (do we want to use VuePress 2?) #52

allow undefined addrs for asset create

currently these lines disable assetCreate's manager, resever, freeze or clawback addresses from being undefined which is sometimes a desired effect...
(for example ARC69 suggests undefined freeze + clawback addresses)

algonautjs/src/index.ts

Lines 529 to 532 in 5c90692

const manager = (args.manager && args.manager.length > 0) ? args.manager : this.account.addr;
const reserve = (args.reserve && args.reserve.length > 0) ? args.reserve : this.account.addr;
const freeze = (args.freeze && args.freeze.length > 0) ? args.freeze : this.account.addr;
const clawback = (args.clawback && args.clawback.length > 0) ? args.clawback : this.account.addr;

Remove accountHasTokens

algonaut.accountHasTokens doesn't do anything:

        /**
	 * Checks if account has at least one token (before playback)
	 * Keeping this here in case this is a faster/less expensive operation than checking actual balance
	 * @param address - Address to check
	 * @param assetIndex - the index of the ASA
	 */
	async accountHasTokens(address: string, assetIndex: number): Promise<any> {
		return 'this is not done yet';
	}

Might be useful but perhaps we just remove it until further notice?

Mostly writing this issue in case any users try to use this method after seeing it in the docs and wonder what's going on :)

allow for algo/asset xfers of 0?

currently atomicSendAlgo and sendAlgo methods disallow transfers of 0

do we want to support this?
sometimes a 0 xfr is needed for an optin or rekeyto txn. should these be explicit methods or should we allow these to happen in the sendAlgo txn or both?

A better website

Algonaut.js needs a better website & documentation! Let's just start over :)

algonaut UI

kit:

  • single sign-on button (+ auth'd state w user identicon)
  • txn status button (signed, submitted, confirmed)

fix transactionSigner on all atomic txns

rn algonaut assumes the txn signer is this.account who created the txn, but sometimes it's not.

how do we want to handle when the txn .from is someone else or if the current account's authAddr is not itself (ie, had been rekeyed)?

api design Q

should we make all methods + config funcs individual exports that relate to a central state instance?
versus how it is now where most utils are members + methods of the large Algonaut() class.


reference:
look how mobx does this w a global configure func (https://mobx.js.org/configuration.html#proxy-support)

import { configure } from "mobx"

configure({
    useProxies: "never"
})

then, instead of how we do it now,
it would look something like...

import { 
  setNodeConfig,
  connect, 
  sendAlgo
} from '@thencc/algonautjs';

// set node to use (globally)
setNodeConfig({
    BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2',
    API_TOKEN: { 'X-API-Key': 'MY_KEY_HERE' }, // key is header, value is token
    LEDGER: 'TestNet',
    PORT: ''
});

// connect acct 
let accts = await connect(); // uses inkey as single wallet default. similar to how it is now. + stores in localstorage

// construct a txn + sign it + send it
let t = await sendAlgo({
  to: '123', 
  amount: 1000
});

idea:

import { 
  AnyWalletState
} from '@thencc/algonautjs';

console.log(AnyWalletState.address); // would be populated on page load IF user had connected an acct to the dapp previously

separate sign + send txn methods

consider doing something like the below where we only sign the txn(s) in the incoming array if we have access to that account's sk.

					// do we have access to the required signer?
					if ((txn.transactionSigner as AlgosdkAccount).addr == this.account?.addr) {
						signedTx = signTransaction(txnGroup[i], this.account.sk);
					}
				}

				if (signedTx) {
					signed.push(signedTx.blob);
				}

add ability to re-init network connected to without let a = new Algonaut({config})

consider dApp that changes from testnet -> mainnet.
(vue doesnt love re-constructing new class instances, can break reactivity if not handled delicately)

// initial network
const config = {
  BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2',
  API_TOKEN: {}
};
const algonaut = new Algonaut(config);
await algonaut.checkStatus();

// update to network settings
const newConfig = {
  BASE_SERVER: 'https://mainnet-algorand.api.purestake.io/ps2', // diff
  API_TOKEN: {}
};
algonaut.setNetwork(newConfig);
await algonaut.checkStatus();

somewhat related... should we have a algonaut.connected boolean? @enceladus

remove init inkey by id

let's just use initSrc. make only 1 path for mounting / unmounting to DOM.

  • consider manual dom el removal (which resets need to happen)

update config structure

const algonaut = new Algonaut({
    BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2',
    INDEX_SERVER: 'https://testnet-algorand.api.purestake.io/idx2',
    LEDGER: 'TestNet',
    PORT: '',
    API_TOKEN: { 'X-API-Key': process.env.API_TOKEN }
});

-->

const algonaut = new Algonaut({
    BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2',
    INDEX_SERVER: 'https://testnet-algorand.api.purestake.io/idx2',
    LEDGER: 'TestNet',
    PORT: '',
    API_TOKEN: process.env.API_TOKEN,
    API_TOKEN_HEADER: 'X-API-Key'
});

thoughts @enceladus ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.