Skip to main content

Advanced usage

This section contains a series of scripts that show complete usage examples of the client library. It should help new developers better understand the library even though the documentation is currently under construction. The import statements are always removed for brevity: any IDE'll automatically find the right ones.

While the scripts are mostly complete, they may not be ready to run. await can only be used in functions and modules, and you'd never run these scripts as standalone applications. You should also define variables, such as key pairs and IDs. They're intended to be used as a reference to discover what functions to call to achieve something.

Create a connection

This basic code lets you set up a connection, which retrieves data from the blockchain using queries without sending any operations. If you're familiar with EVMs, it's similar to having no wallet access. You can call view and pure functions (queries), but not others (operations).

const url = "http://localhost:7740";
const client = await createClient({
nodeURLPool: url,
blockchainIID: 0,
});

// This can be used for most queries
// Code completion will allow you to check what's available
const connection = createConnection(client);

//Example:
const account = await connection.getAccountById(
"5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A07169EAD2"
);

// This account will unlock new queries for us.
// You can't call operations with it! You haven't given access to
// Aany keypair here. Use code completion to check what's available!

//Example:
const balances = await account.getBalances();

Dealing with paginated entries

Most queries that return multiple entities'll return them in PaginatedEntity. This means you'll retrieve a certain amount of them, and to retrieve more, you have to query them again, specifying where to start.

const url = "http://localhost:7740";
const client = await createClient({
nodeURLPool: url,
blockchainIID: 0,
});
const connection = createConnection(client);

// This is a paginated query:
// It will contain 100 entries and a cursor pointing to the next page
const assetsPage1 = await connection.getAllAssets();

console.log(assetsPage1.data);
// [asset1, asset2, ..., asset100]

// if I only need 3 assets, I can limit the number of entries per page
const assetsPage1Short = await connection.getAllAssets(3);

// I want the next three now:
const assetsPage2Short = await connection.getAllAssets(
3,
assetsPage1Short.nextCursor
);

Logging into an account

When a user has an account on the chain, the best way to operate it's to give them access to the account through their Web3 provider. This can be done simply. When an account has been registered with the Web3 provider through the auth server, it'll have an auth descriptor that allows that EVM address to operate over it. This can then be used to log into the account:

const url = "http://localhost:7740";
const client = await createClient({
nodeURLPool: url,
blockchainIID: 0,
});

//Interact with Metamask or similar...
const { getSession } = createKeyStoreInteractor(
client,
await createWeb3ProviderEvmKeyStore(window.ethereum)
);

//... to retrieve the session...
const session = await getSession(accountId);

//... which lets us use the AuthenticatedAccount object...
await session.account.burn(assetId, amount);

//... and do low level calls with our account
await session.call(op("my_op", arg1, arg2));

Automatic signatures

In a more sophisticated scenario, you want to avoid asking the user to sign every transaction. Sometimes, there might be routine transactions that don't expose security threats. In this case, the wallet can authorize a session that signs them in the background. You should limit the authorization of this background session so that it only performs zero—threat operations. This scenario is better explained in the Login manager paragraph.

Suppose you defined a new flag for zero—threat transactions and you called it 0. You can see here how that can be done: flags = ["0"] would allow you to do so. This is how you get a session that asks you to connect to Metamask and makes you approve the session for automatic signature of operations with flag 0.

const url = "http://localhost:7740";
const client = await createClient({
nodeURLPool: url,
blockchainIID: 0,
});

//Interact with Metamask or similar...
const keyStoreInteractor = createKeyStoreInteractor(
client,
await createWeb3ProviderEvmKeyStore(window.ethereum)
);

const session = await keyStoreInteractor.getLoginManager().login({
accountId: id,
flags: ["0"], //allow any `0`-flag operation without asking for a signature
});

//Now you can call any operation:

//this has "T" flag. It will require a Metamask signature
session.account.transfer(/*parameters*/);

//this has "A" flag. It will require a Metamask signature
session.account.addAuthDescriptor(/*parameters*/);

//suppose this has "0" flag. It will NOT require a Metamask signature
session.call(op("my_0_flag_operation" /*parameters*/));

//suppose this has "0" AND "T" flag. It WILL require a Metamask signature
session.call(op("my_0_and_T_flag_operation" /*parameters*/));

Signatures

Here we'll explain signatures and signature providers more in—depth. This code is only needed for advanced use cases. For standard postchain + EVM usage, you can skip this section.

//this is a simple key pair
//it has a pubKey and a privKey property
const kp = encryption.makeKeyPair();

//this is a signature provider
//it hides away the privKey by only exposing
//pubKey and sign()
const sp = newSignatureProvider(kp);

//you can also create it without ever touching the keypair
const sp2 = newSignatureProvider();

//in both cases, the privateKey is on the machine. If you need
//to sign with a different solution (e.g. an already generated
//keypair from a different app or a hardware wallet) you need
//to create your own signatureProvider. It's quite easy:
/*
const sp3 = {
pubKey: Buffer.from("032846C2EDB843E37D63A128C033788C924D30C5BA51FE8E7AD81A2D748839F2B0", "hex")
sign: async (digest: Buffer) => {
//custom code to sign
return signature
}
}
*/
//the sign function is async to allow the code to wait for user interaction.

Auth descriptors

Let's see how to build an auth descriptor. The following code is'nt complete, as you would have to create keypairs for any auth descriptor.

// an auth descriptor has arguments and rules:
// arguments: who can access it, and what can they do
// rules: when is the auth descriptor active

//"whenever pubkey signs, they can edit the account or transfer funds"
//whenever -> no expiration rules
//pubkey signs -> single signature
//edit the account -> "A" flag
//transfer funds -> "T" flag
let authDesc = authDescriptor.create.singleSig.withArgs(
["A", "T"],
pubKey
).andNoRules;

//"whenever pubkey1 AND pubkey2 sign, they can transfer funds"
//pubKey1 AND pubkey2 -> 2 signatures required
//note that it's now a multiSig, and it has no "A" flag
authDesc = authDescriptor.create.multiSig.withArgs(["T"], 2, [
pubKey1,
pubKey2,
]).andNoRules;

//"whenever pubkey1 OR pubkey2 sign, they can edit the account"
//pubKey1 OR pubkey2 -> 1 signature required
authDesc = authDescriptor.create.multiSig.withArgs(["A"], 1, [
pubKey1,
pubKey2,
]).andNoRules;

//"whenever pubkey1 OR pubkey2 sign, they can edit the account"
//pubKey1 OR pubkey2 -> 1 signature required
authDesc = authDescriptor.create.multiSig.withArgs(["A"], 1, [
pubKey1,
pubKey2,
]).andNoRules;

//"The first time 3 out of 5 keys sign, they can edit the account"
//3 out of 5 -> 5 signatures required
//The first time -> expire after one use
authDesc = authDescriptor.create.multiSig
.withArgs(["A"], 3, [pubKey1, pubKey2, pubKey3, pubKey4, pubKey5])
.andRules(authDescriptor.allow.operationCount.equals(1).only);

//all rules are defined under authDescriptor.allow
const lessThanThreeTimes = authDescriptor.allow.operationCount.lessThan(3).only;
const untilBlock100 = authDescriptor.allow.blockHeight.lessOrEqual(100).only;
const during2024 = authDescriptor.allow.blockTime
.greaterOrEqual(1704067200000) //Jan 1st, 2024 (millisecond timestamp)
.and.blockTime.lessThan(1735689600000).only; //Jan 1st, 2025 (millisecond timestamp)
const onceDuring2024 = authDescriptor.allow.blockTime
.greaterOrEqual(1704067200000) //Jan 1st, 2024 (millisecond timestamp)
.and.blockTime.lessThan(1735689600000) //Jan 1st, 2025 (millisecond timestamp)
.and.operationCount.equals(1).only;

Using admin functions

This code should never be used in production client applications, as that would leak the admin private keys giving everyone full access to the postchain. However, these functions can be useful for setup code, testing, and other more specific applications.

const url = "http://localhost:7740";
const client = await createClient({
nodeURLPool: url,
blockchainIID: 0,
});

const adminSigProv = newSignatureProvider(
encryption.makeKeyPair(
"2AC313A8384F319058C578F0E46A9871EACE285EA9144166D80FACE635713D39"
)
);

const accountSignatureProvider = newSignatureProvider();

const authDesc = authDescriptor.create.singleSig.withArgs(
["A", "T"],
accountSignatureProvider.pubKey
).andNoRules;

await registerAccount(client, adminSigProv, authDesc);