Use npm
due to its imcompatibility with yarn
.
npm i aonote
aoNote SDK goes beyond Atomic Notes, streamlining Arweave/AO development with elegant syntax enhancements and seamless message piping for an enjoyable coding experience.
import { AR, AO, Profile, Notebook, Note } from "aonote"
AR
handles operations on the base Arweave Storage layer as well as wallet connections.
- Instantiate
- Set or Generate Wallet
- toAddr
- mine
- balance | toAR | toWinston
- transfer
- checkWallet
- post
- tx
- data
- bundle
const ar = new AR()
host
, port
, and protocol
can be set to access a specific gateway rather than https://arweave.net
.
const ar = new AR({ host: "localhost", port: 4000, protocol: "http" })
In case of local gateways, you can only set port
and the rest will be automatically figured out.
const ar = new AR({ port: 4000 })
You can initialize AR with a wallet JWK or ArConnect.
const ar = await new AR().init(jwk || arweaveWallet)
Or you can generate a new wallet. In case of ArLocal, you can mint AR at the same time.
const { jwk, addr, pub, balance } = await ar.gen("100") // mint 100 AR
Once a wallet is set in one of these 3 ways, you cannot use the instance with another wallet unless you re-initialize it with another wallet. This is to prevent executing transactions with the wrong wallet when the browser connected active address has been changed unknowingly.
You can go on without calling init
or gen
, in this case, AR generates a random wallet when needed, and also using different wallets will be allowed. This is useful, if you are only calling dryrun
with AO, since AO requires a signature for dryrun
too, but you don't want to bother the user by triggering the browser extension wallet for read only calls.
Once a wallet is set, ar.jwk
and ar.addr
will be available.
Convert a jwk to the corresponding address.
const addr = await ar.toAddr(jwk)
Mine pending blocks (only for arlocal).
await ar.mine()
Get the current balance of the specified address in AR. addr
will be ar.addr
if omitted.
const balance_AR = await ar.balance() // get own balance
const balance_Winston = ar.toWinston(balance_AR)
const balance_AR2 = ar.toAR(balance_Winston)
const balance_AR3 = await ar.balance(addr) // specify wallet address
Transfer AR token. amount
is in AR, not in winston for simplicity.
const { id } = await ar.transfer(amount, to)
You can set a jwk to the 3rd parameter as a sender, otherwise the sender is ar.jwk
.
const { id } = await ar.transfer(amount, to, jwk)
For most write functions, jwk
can be specified as the last parameter or a field like { data, tags, jwk }
.
checkWallet
is mostly used internally, but it returns this.jwk
if a wallet has been assigned with init
, or else it generates a random wallet to use. The following pattern is used in many places. With this pattern, if a wallet is set with init
and the jwk
the user is passing is different, checkWallet
produces an error to prevent the wrong wallet. If no wallet has been set with init
or gen
and the jwk
is not passed, it generates and returns a random wallet.
some_class_method({ jwk }){
let err = null
;({ err, jwk } = await ar.checkWallet({ jwk }))
if(!err){
// do domething with the jwk
}
}
Post a data to Arweave.
const { err, id } = await ar.post({ data, tags })
tags
are not an Array but a hash map Object for brevity.
const tags = { "Content-Type": "text/markdown", Type: "blog-post" }
If you must use the same name for multiple tags, the value can be an Array.
const tags = { Name: [ "name-tag-1", "name-tag-2" ] }
Get a transaction.
const tx = await ar.tx(txid)
Get a data.
const data = await ar.data(txid, true) // true if string
Bundle ANS-104 dataitems.
const { err, id } = await ar.bundle(dataitems)
dataitems
are [ [ data, tags ], [ data, tags ], [ data, tags ] ]
.
const { err, id } = await ar.bundle([
[ "this is text", { "Content-Type": "text/plain" }],
[ "# this is markdown", { "Content-Type": "text/markdown" }],
[ png_image, { "Content-Type": "image/png" }]
])
- Instantiate
- deploy
- msg
- dry
- asgn
- load
- eval
- spwn
- aoconnect Functions
- postModule
- postScheduler
- wait
- Function Piping
You can initialize AO in the same way as AR.
const ao = await new AO().init(arweaveWallet || jwk)
If you need to pass AR settings, use ar
. ao.ar
will be automatically available.
const ao = await new AO({ ar: { port: 4000 }}).init(arweaveWallet || jwk)
const addr = ao.ar.addr
await ao.ar.post({ data, tags })
Spawn a process, get a Lua source, and eval the script. src
is an Arweave txid of the Lua script.
const { err, res, pid } = await ao.deploy({ data, tags, src, fills })
fills
replace the Lua source script from src
.
local replace_me = '<REPLACE_ME>'
local replace_me_again = '<REPLACE_ME_AGAIN>'
local replace_me_with_hello_again = '<REPLACE_ME>'
const fills = { REPLACE_ME: "hello", REPLACE_ME_AGAIN: "world" }
This will end up in the following lua script.
local replace_me = 'hello'
local replace_me_again = 'world'
local replace_me_with_hello_again = 'hello'
In case you have multiple scripts, use loads
and pass src
and fills
respectively.
await ao.deploy({ tags, loads: [ { src, fills }, { src: src2, fills: fills2 } ] })
Send a message.
const { err, mid, res, out } = await ao.msg({
data, action, tags, check, checkData, get
})
check
determins if the message call is successful by checking through Tags
in Messages
in res
.
const check = { "Status" : "Success" } // succeeds if Status tag is "Success"
const check2 = { "Status" : true } // succeeds if Status tag exists
checkData
checks Data
field instead of Tags
.
const checkData = "Success" // succeeds if Data field is "Success"
const checkData2 = true // succeeds if Data field exists
get
will return specified data via out
.
const get = "ID" // returns the value of "ID" tag
const get2 = { name: "Profile", json: true } // "Profile" tag with JSON.parse()
const get3 = { data: true, json: true } // returns Data field with JSON.parse()
Dryrun a message without writing to Arweave.
const { err, res, out } = await ao.dry({
data, action, tags, check, checkData, get
})
Assign an existing message to a process.
const { err, mid, res, out } = await ao.asgn({ pid, mid, check, checkData, get })
Get a Lua source script from Arweave and eval it on a process.
const { err, res, mid } = await ao.load({ src, fills, pid })
Eval a Lua script on a process.
const { err, res, mid } = await ao.eval({ pid, data })
Spawn a process. module
and scheduler
are auto-set if omitted.
const { err, res, pid } = await ao.spwn({ module, scheduler, tags, data })
The original aoconnect functions message
| spawn
| result
| assign
| dryrun
are also available.
createDataItemSigner
is available as toSigner
.
const signer = ao.toSigner(jwk)
const process = await ao.spawn({ module, scheduler, signer, tags, data })
const message = await ao.message({ process, signer, tags, data })
const result = await ao.result({ process, message })
data
should be wasm binary. overwrite
to replace the default module set to the AO instance.
const { err, id: module } = await ao.postModule({ data, jwk, tags, overwrite })
This will post Scheduler-Location
with the jwk
address as the returning scheduler
.
const { err, scheduler } = await ao.postScheduler({ url, jwk, tags, overwrite })
wait
untill the process becomes available after spwn
. This is mostly used internally with deploy
.
const { err } = await ao.wait({ pid })
Most functions return in the format of { err, res, out, pid, mid, id }
, and these function can be chained with pipe
, which makes executing multiple messages a breeze.
For example, following is how deploy
uses pipe
internally. The execusion will be immediately aborted if any of the functions in fns
produces an error.
let fns = [
{
fn: "spwn",
args: { module, scheduler, tags, data },
then: { "args.pid": "pid" },
},
{ fn: "wait", then: { "args.pid": "pid" } },
{ fn: "load", args: { src, fills }, then: { "args.pid": "pid" } }
]
const { err, res, out, pid } = await this.pipe({ jwk, fns })
If the function comes from other instances rather than AO
, use bind
.
const fns = [{ fn: "post", bind: this.ar, args: { data, tags }}]
You can pass values between functions with then
. For instance, passing the result from the previous functions to the next function's arguments is a common operation.
const fns = [
{ fn: "post", bind: ao.ar, args: { data, tags }, then: ({ id, args, out })=>{
args.tags.TxId = id // adding TxId tag to `msg` args
out.txid = id // `out` will be returned at last with `pipe`
}},
{ fn: "msg", args: { pid, tags }},
]
const { out: { txid } } = await ao.pipe({ fns, jwk })
If then
returns a value, pipe
will immediately return with that single value. You can also use err
to abort pipe
with an error.
const fns = [
{ fn: "msg", args: { pid, tags }, then: ({ inp })=>{
if(inp.done) return inp.val
}},
{ fn: "msg", args: { pid, tags }, err: ({ inp })=>{
if(!inp.done) return "something went wrong"
}},
]
const val = await ao.pipe({ jwk, fns })
then
has many useful parameters.
res
:res
from the previous resultargs
:args
for the next functionout
:out
the finalout
result from thepipe
sequenceinp
:out
from the previous result_
: if values are assigned to the_
fields,pipe
returns them as top-level fields in the endpid
:pid
will be passed if any previous functions returnspid
( e.g.deploy
)mid
:mid
will be passed if any previous functions returnsmid
( e.g.msg
)id
:id
will be passed if any previous functions returnsid
( e.g.post
)
then
can be a simplified hashmap object.
let fns = [
{
fn: "msg",
args: { tags },
then: { "args.mid": "mid", "out.key": "inp.a", "_.val": "inp.b" },
},
{ fn: "some_func", args: {} } // args.mid will be set from the previous `then`
]
const { out: { key }, val } = await ao.pipe({ jwk, fns })
cb
can report the current progress of pipe
after every function execution.
await ao.pipe({ jwk, fns, cb: ({ i, fns, inp })=>{
console.log(`${i} / ${fns.length} functions executed`)
}})
All the arguments are optional. profile.ao
and profile.ar
will be available.
const profile = await new Profile({
registry, registry_src, profile_src, ar, ao
}).init( jwk || arweaveWallet )
If you init
a Profile, it will resolve the AO profileId
associated with the wallet address.
const profile = await new Profile({}).init(jwk)
const profileId = profile.id
const { pid: id } = await ao.createProfile({ profile }))
A list of ids managed by the provided address. addr
will be ao.addr
if omitted.
const ids = await profile.ids({ addr })
A profile of the provided id. id
will be ao.id
if omitted.
const profile = await profile.profile({ id })
Multiple profiles.
const profiles = await profile.profiles({ ids })
Update an AO profile.
const { err, res } = await profile.updateProfile({ id, profile })
Information ( profile, assets, collections }
of the provided id. id
will be ao.id
if omitted.
const { out: info } = await profile.info({ id })
You can create a new AO profile registry, which is useful for local testing.
const { err, pid } = await profile.createResistry()
All arguments are optional. collection.profile
, collection.ao
, collection.ar
will be available.
const collection = await new Collection({
pid, registry, registry_src, thumbnail, banner, collection_src, profile, ar, ao
}).init( jwk || arweaveWallet )
If bazar
is true, the newly created collection will be registered to the Bazar collection registry.
const collection = new Collection({ ao })
const { pid: collection_pid } = await collection.create({
info: { title, description, thumbnail, banner }, bazar
})
const { out: info } = await collection.info()
At leadt one field is required.
const { err } = await collection.updateInfo({
title, description, thumbnail, banner
})
const { err } = await collection.addAsset(asset_pid)
const { err } = await collection.removeAsset(asset_pid)
const { err } = await collection.addAssets(asset_pids)
const { err } = await collection.removeAssets(asset_pids)
You can create a new collection registry.
const { err, pid } = await collection.createRegistry()
All arguments are optional. asset.profile
, asset.ao
, asset.ar
will be available.
const asset = await new Asset({
pid, asset_src, ar, ao, profile
}).init( jwk || arweaveWallet)
const asset = new Asset({ ao })
const { pid: asset_pid } = await asset.create({
data,
content_type,
info: { title, description, thumbnail, banner },
udl: { payment, access, derivations, commercial, training },
token: { fraction },
})
await collection.addAsset(pid) // add to a collection
- payment
- mode :
single
|random
|global
- recipient
- mode :
- access
- mode :
none
|one-time
- fee
- mode :
- derivations
- mode :
allowed
|disallowed
- term :
credit
|indication
|passthrough
|revenue
|monthly
|one-time
- share
- fee
- mode :
- commercial
- mode :
allowed
|disallowed
- term :
revenue
|monthly
|one-time
- mode :
- training
- mode :
allowed
|disallowed
- term :
monthly
|one-time
- mode :
const { out: info } = await asset.info()
An atomic asset is basically an ao Token.
const { err, out: balance } = await asset.balance({ target })
const { err, out: balances } = await asset.balances()
const { err } = await asset.mint({ quantity })
const { err } = await asset.transfer({ recipint, quantity })
If you want to transfer token from the AO profile rather than the profile owner wallet, set profile
to true
. The token will be transferred from asset.profile.id
instead of asset.ar.addr
.
const { err } = await asset.transfer({ recipint, quantity, profile:: true })
Notebook
inherites everything from Collection
with a bit of name changes in some methods.
All arguments are optional. notebook.profile
, notebook.ao
, notebook.ar
will be available.
const notebook = await new Notebook({
pid, registry, registry_src, thumbnail, banner, notebook_src, profile, ar, ao
}).init( jwk || arweaveWallet )
const { err } = await notebook.addNote(note_pid)
const { err } = await notebook.removeNote(note_pid)
const { err } = await notebook.addNotes(note_pids)
const { err } = await notebook.removeNotes(note_pids)
Note
inherites everything from Asset
with some added functions specific to atomic-notes.
All arguments are optional. note.profile
, note.ao
, note.ar
will be available.
const note = await new Note({
pid, note_src, notelib_src, proxy, render_with, ar, ao, profile
}).init( jwk || arweaveWallet)
const { err } = await asset.updateInfo({ title, description, thumbnail })
const { out: versions } = await note.list()
If version
is omitted, it returns the latest version.
const { out: atomic_note } = await note.get(version)
const { err } = await note.update(data, version)
version
should be higher than the current version, or it can be major
, minor
, or patch
for automatically bumping the respective part of the current version.
const { out: editors } = await note.editors()
const { err, out: editors } = await note.addEditor(editors)
const { err, out: editors } = await note.removeEditor(editors)