1.0.2-beta • Published 12 months ago

@hashgreed/kss-signer v1.0.2-beta

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

Signer

Overview

KSS Signer is a TypeScript/JavaScript library that features signing and broadcasting transactions on behalf of users without asking them for their seed phrases or private keys.

Provider

In order to work with Signer, you need to link an external Provider library. Provider securely stores user's private data. Your web app and Signer itself do not have access to user's private key and seed phrase.

The Provider authenticates user and generates a digital signature.

Signer implements developer-friendly protocol for interacting with Provider as well as broadcasts transactions to the blockchain. npm.io

For now, you can use one of the following Providers:

  • ProviderSeed developed by Waves team creates user account from SEED. ProviderSeed can be used at the app debugging stage.
  • ProviderWeb developed by Waves.Exchange team uses an account created or imported into the Waves.Exchange web app via user's private key or seed phrase.
  • ProviderCloud developed by Waves.Exchange team uses an email-based Waves.Exchange.

You can also develop your own Provider, see the Provider Interface section below.

Signer + ProviderWeb: How It Works

When Signer requests to sign a transaction, ProviderWeb opens an iframe, where the user can review transaction details and confirm or reject it. Upon confirmation, ProviderWeb generates a digital signature.

Restrictions

Signer supports all types of transactions except Exchange transaction and Update Asset Info transaction.

Signer supports all browsers except Brave.

Getting Started

1. Signer and Provider library installation

  • To install Signer library use

    npm i @hashgreed/kss-signer
  • To install ProviderSeed developed by Waves team, use

    npm i @waves/provider-seed @waves/waves-transactions
  • To install ProviderWeb developed by Waves.Exchange, use

    npm i @waves.exchange/provider-web

    For Windows, use the following format:

    npm i '@waves.exchange/provider-web'
  • To install ProviderCloud developed by Waves.Exchange, use

    npm i @waves.exchange/provider-cloud

    For Windows, use the following format:

    npm i '@waves.exchange/provider-cloud'

2. Library initialization

Add library initialization to your app.

  • For Testnet & ProviderSeed:

    import { Signer } from '@hashgreed/kss-signer';
    import { ProviderSeed } from '@waves/provider-seed';
    import { libs } from '@waves/waves-transactions';
    
    const seed = libs.crypto.randomSeed(15);
    const signer = new Signer({
      // Specify URL of the node on Testnet
      NODE_URL: 'https://nodes.krossexplorer.com'
    });
    signer.setProvider(new ProviderSeed(seed));
  • For Testnet & Waves.Exchange ProviderWeb:

    import { Signer } from '@hashgreed/kss-signer';
    import { ProviderWeb } from '@waves.exchange/provider-web';
    
    const signer = new Signer({
      // Specify URL of the node on Testnet
      NODE_URL: 'https://nodes.krossexplorer.com'
    });
    signer.setProvider(new ProviderWeb('https://testnet.waves.exchange/signer/'))
  • For Testnet & Waves.Exchange ProviderCloud:

    import { Signer } from '@hashgreed/kss-signer';
    import { ProviderCloud } from '@waves.exchange/provider-cloud';
    
    const signer = new Signer({
      // Specify URL of the node on Testnet
      NODE_URL: 'https://nodes.krossexplorer.com'
    });
    signer.setProvider(new ProviderCloud('https://testnet.waves.exchange/signer/'))
  • For Mainnet & Waves.Exchange ProviderWeb:

    import { Signer } from '@hashgreed/kss-signer';
    import { ProviderWeb } from '@waves.exchange/provider-web';
    
    const signer = new Signer();
    signer.setProvider(new ProviderWeb());
  • For Mainnet & Waves.Exchange ProviderCloud:

    import { Signer } from '@hashgreed/kss-signer';
    import { ProviderCloud } from '@waves.exchange/provider-cloud';
    
    const signer = new Signer();
    signer.setProvider(new ProviderCloud());

After that you will be able to use Signer features in the app.

3. Basic example

Now your application is ready to work with Waves blockchain. Let's test it by implementing basic functionality. For example, we could try to authenticate user, get his/her balances and transfer funds.

const user = await signer.login();
const balances = await signer.getBalance();
const [broadcastedTransfer] = await signer
  .transfer({amount: 100000000, recipient: 'alias:T:merry'}) // Transfer 1 KSS to alias merry
  .broadcast(); // Promise will resolved after user sign and node response

const [signedTransfer] = await signer
  .transfer({amount: 100000000, recipient: 'alias:T:merry'}) // Transfer 1 KSS to alias merry
  .sign(); // Promise will resolved after user sign

More examples

See example of an app that implements the donate button: https://github.com/vlzhr/crypto-donate.

Constructor

new Signer({
  NODE_URL: 'string',
})

Creates an object that features the following methods.

Parameters:

ParameterDefault valueDescription
NODE_URLhttps://nodes.krossexplorer.comNode that is used to access a blockchain

Methods

In code you can use TypeScript types.

User Info

login

Authenticates user with his/her account; creates account if it don't exist.

login();

Returns: Promise of user data: address and public key.

Usage:

const {address, publicKey} = await signer.login();

Output example:

{
  address: '3P8pGyzZL9AUuFs9YRYPDV3vm73T48ptZxs',
  publicKey: 'FuChbN7t3gvW5esgARFytKNVuHSCZpXSYf1y3eDSruEN',
}

logout

Logs user out.

logout();

Returns: Promise\<void>.

Usage:

await signer.logout();

getBalance

If user logged in, provides balances of assets in user's portfolio.

getBalance();

Returns: Promise of list of balances.

Usage:

const balances = await signer.getBalance();

Output example:

[{
  assetId: 'KSS',
  assetName: 'Krosscoin',
  decimals: 8,
  amount: 100000000,
  isMyAsset: false,
  tokens: 1,
  sponsorship: null,
  isSmart: false
},
{
  assetId: 'AcrRM9STdBu5PNiFveTCbRFTS8tADhKcsbC2KBp8A4tx',
  assetName: 'CoffeeCoin',
  decimals: 3,
  amount: 1500,
  isMyAsset: false,
  tokens: 1.5,
  isSmart: false,
  sponsorship: 500
}]

Output fields:

Field nameDescription
assetIdBase58 encoded ID of the asset
assetNameName of the asset
decimalsNumber of decimal places in the asset amount
amountAmount of asset multiplied by 10^decimals. For example, decimals of KSS is 8, so the real amount is multipied by 10^8. { "KSS": 677728840 } means 6.77728840
isMyAssettrue if current user is an asset issuer
tokensAmount of asset to display in app interface
sponsorshipAmount of sponsored asset to be charged to users (per 0.001 KSS) multiplied by 10^decimalsnull if the asset is not sponsored
isSmarttrue for smart assets

getSponsoredBalances

If user logged in, provides balances of sponsored assets in user's portfolio. See Sponsored Fee.

getSponsoredBalances();

Returns: Promise of list of balances.

Usage:

const sponsoredBalances = await signer.getSponsoredBalances();

Output example:

[{
  assetId: 'AcrRM9STdBu5PNiFveTCbRFTS8tADhKcsbC2KBp8A4tx',
  assetName: 'CoffeeCoin',
  decimals: 3,
  amount: 1500,
  isMyAsset: false,
  tokens: 1.5,
  isSmart: false,
  sponsorship: 500
}]

Output fields are the same as in getBalance method.

Create transactions

The following methods create transactions (but do not sign or broadcast them):

Check which of these transactions are supported by your Provider.

Common fields

Each create transaction method has optional fields that you don't specify manually in most cases:

Field nameDescriptionDefault value
chainId'W'.charCodeAt(0) or 78 means Mainnet'T'.charCodeAt(0) or 84 means TestnetDefined by configuration of Waves node that is set in Constructor
feeTransaction feeCalculated automatically as described in Transaction fee section
proofsArray of transaction signaturesAdded by sign or broadcast method (see How to Sign and Broadcast Transactions). If you specify a proof manually, it is also added to the array
senderPublicKeyBase58 encoded public key of transaction senderReturned by login method

How to Sign and Broadcast Transactions

Each create transaction method returns object that features the sign and broadcast methods.

To sign transaction use sign method. For example:

signer.invoke({
   dApp: address,
   call: { function: name, args: convertedArgs },
}).sign();

To sign transaction and immediately send it to blockchain use broadcast method. For example:

signer.invoke({
   dApp: address,
   call: { function: name, args: convertedArgs },
}).broadcast();

Note: this broadcast method has the same options as the signer.broadcast method that is described below.

You can sign or broadcast several transactions at once. For example:

signer.alias({ 'new_alias', })
  .data([{ key: 'value', type: 'integer', value: 1 ])
  .transfer({ recipient: '3P8pGyzZL9AUuFs9YRYPDV3vm73T48ptZxs', amount: 10000 })
}).broadcast();

alias

Creates Create Alias transaction.

alias(data: {
  alias: 'string'
})

Parameters:

Parameter nameDefault valueDescription
alias*Short and easy to remember name of address. See Alias for more information

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  alias: 'new_alias',
}

const [tx] = await signer
  .alias(data)
  .broadcast();

burn

Creates Burn transaction.

burn(data: {
    assetId*: 'string',
    quantity*: LONG,
})

Parameters:

Parameter nameDefault valueDescription
assetId*Base58 encoded ID of the asset to burn
quantity*Amount of asset multiplied by 10^decimals. For example, decimals of KSS is 8, so the real amount is multipied by 10^8. { "KSS": 677728840 } means 6.77728840

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  assetId: '4uK8i4ThRGbehENwa6MxyLtxAjAo1Rj9fduborGExarC',
  quantity: 100,
}

const [tx] = await signer
  .burn(data)
  .broadcast();

cancelLease

Creates Lease Cancel transaction.

cancelLease(data: {
    leaseId: 'string',
})

Parameters:

Parameter nameDefault valueDescription
leasetId*Base58 encoded ID of the Lease transaction

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  leaseId: '69HK14PEHq2UGRfRYghVW8Kc3487uJaoUmk2ntT4kw7X',
}

const [tx] = await signer
  .cancelLease(data)
  .broadcast();

data

Creates Data transaction.

data(data: [{
  key: 'string',
  type: 'string' | 'integer' | 'binary' | 'boolean',
  value: 'string' | number | boolean,
])

Parameters:

Parameter nameDefault valueDescription
key*Key of a record. Maximum of 100 characters
typeType of a record
value*Value of a record. Maximum of 5 Kbytes

* Required parameter.

See Common fields for other fields description.

Usage:

const records = [
  { key: 'name', type: 'string', value: 'Lorem ipsum dolor sit amet' },
  { key: 'value', type: 'integer', value: 1234567 },
  { key: 'flag', type: 'boolean', value: true }
]

const [tx] = await signer
  .data({ data: records })
  .broadcast();

invoke

Creates Invoke Scipt transaction.

invoke(data: {
  dApp: 'string',
  fee: LONG,
  payment: [{
    assetId: 'string',
    amount: LONG,
  }],
  call: {
    function: 'string',
    args: [{
      type: 'integer' | 'string' | 'binary',
      value: number | 'string',
    }],
  },
  feeAssetId: 'string',
})

Parameters:

Parameter nameDefault valueDescription
dApp*Base58 encoded address or alias (with alias:T: prefix) of the dApp whose script should be invoked
feeWe recommend to specify fee depending on number of action performed by called function (see Transaction Fee)
paymentPayments attached to the transaction. Maximum of two payments
payment.assetId*Base58 encoded ID of the asset to pay. KSS or null means KSS
payment.amount*Amount of asset multiplied by 10^decimals. For example, decimals of KSS is 8, so the real amount is multipied by 10^8. { "KSS": 677728840 } means 6.77728840
callDefault function should be invoked in the dAppParameters for called function
call.function*Name of the function that is called
call.args*Arguments for the function that is called
call.args.type*Type of argument
call.args.value*Value of argument
feeAssetIdKSSBase58 encoded ID of the sponsored asset to pay the fee. See the Sponsored Fee article for more information. null or omitted field means KSS

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  dApp: '3Fb641A9hWy63K18KsBJwns64McmdEATgJd',
  fee: 1000000,
  payment: [{
    assetId: '73pu8pHFNpj9tmWuYjqnZ962tXzJvLGX86dxjZxGYhoK',
    amount: 7,
  }],
  call: {
    function: 'foo',
    args: [
      { type: 'integer', value: 1 },
      { type: 'binary', value: 'base64:AAA=' },
      { type: 'string', value: 'foo' }
    ],
  },
}

const [tx] = await signer
  .invoke(data)
  .broadcast();

issue

Creates Issue transaction.

issue(data: {
  name: 'string',
  decimals: number,
  quantity: LONG,
  reissuable: boolean,
  description: 'string',
  script: 'string',
})

Parameters:

Parameter nameDefault valueDescription
name*Asset name
decimals*Number of digits in decimal part
quantity*Amount of asset multiplied by 10^decimals
reissuable*true – asset reissue is possible.false — asset reissue is not possible
description*Asset description
scriptBase64 encoded script (with base64: prefix) to be attached to to asset

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  name: 'MyToken',
  decimals: 8,
  quantity: 100000000000,
  reissuable: true,
  description: 'It is a gaming token',
}

const [tx] = await signer
  .issue(data)
  .broadcast();

lease

Creates Lease transaction.

lease(data: {
    amount: LONG,
    recipient: 'string',
})

Parameters:

Parameter nameDefault valueDescription
amount*Amount of KSS multiplied by 10^8. For example, { "KSS": 677728840 } means 6.77728840
recipient*Base58 encoded address or alias (with alias:T: prefix) of the recipient

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
    amount: 10000,
    recipient: 'alias:T:merry',
}

const [tx] = await signer
  .lease(data)
  .broadcast();

massTransfer

Creates Mass Transfer transaction.

massTransfer(data: {
  assetId: 'string',
  transfers: [{
    amount: LONG,
    recipient: 'string',
  }],
  attachment: 'string',
})

Parameters:

Parameter nameDefault valueDescription
assetIdKSSBase58 encoded ID of the asset to transfer
transfers*List of transfers
transfers.amount*Amount of asset multiplied by 10^decimals. For example, decimals of KSS is 8, so the real amount is multipied by 10^8. { "KSS": 677728840 } means 6.77728840
transfers.recipient*Base58 encoded address or alias (with alias:T: prefix) of the recipient
attachmentOptional binary data base58 encoded. This field is often used to attach a comment to the transaction. The maximum data size is 140 bytes

* Required parameter.

See Common fields for other fields description.

Usage:

const crypto = require('@waves/ts-lib-crypto')

const data = {
    transfers: [
    {
      amount: 100,
      recipient: '3P23fi1qfVw6RVDn4CH2a5nNouEtWNQ4THs',
    },
    {
      amount: 200,
      recipient: 'alias:T:merry',
    }],
    attachment: crypto.base58Encode(crypto.stringToBytes('sample message for recipient'))
]

const [tx] = await signer
  .massTransfer(data)
  .broadcast();

reissue

Creates Reissue transaction.

reissue(data: {
  assetId: 'string',
  quantity: LONG,
  reissuable: boolean,
})

Parameters:

Parameter nameDefault valueDescription
assetId*Base58 encoded ID of the asset to reissue
quantity*Amount of asset multiplied by 10^decimals to reissue
reissuable*true – asset reissue is possible.false — asset reissue is not possible

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  assetId: 'AcrRM9STdBu5PNiFveTCbRFTS8tADhKcsbC2KBp8A4tx'
  quantity: 100000000000,
  reissuable: true,
}

const [tx] = await signer
  .reissue(data)
  .broadcast();

setAssetScript

Creates Set Asset Script transaction.

setAssetScript(data: {
  assetId: 'string',
  script: 'string',
})

Parameters:

Parameter nameDefault valueDescription
assetId*Base58 encoded ID of the asset
scriptBase64 encoded script (with base64: prefix) to be attached to the asset

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  assetId: 'AcrRM9STdBu5PNiFveTCbRFTS8tADhKcsbC2KBp8A4tx',
  script: 'base64:AwZd0cYf',
}

const [tx] = await signer
  .setAssetScript(data)
  .broadcast();

setScript

Creates Set Script transaction.

setScript(data: {
  script: 'string',
})

Parameters:

Parameter nameDefault valueDescription
scriptBase64 encoded account script or dApp script (with base64: prefix) to be attached to the user account. null means cancelling the script

See Common fields for other fields description.

Usage:

const data = {
  script: 'base64:AAIDAAAAAAAAAAQIARIAAAAAAAAAAA...',
}

const [tx] = await signer
  .setScript(data)
  .broadcast();

sponsorship

Creates Sponsor Fee transaction.

sponsorship(data: {
    assetId: 'string',
    minSponsoredAssetFee: LONG,
})

Parameters:

Parameter nameDefault valueDescription
assetId*Base58 encoded ID of the asset
minSponsoredAssetFeeRequired amount of sponsored token to be charged to users (per 0.001 KSS) multiplied by 10^decimals

* Required parameter.

See Common fields for other fields description.

Usage:

const data = {
  assetId: 'AcrRM9STdBu5PNiFveTCbRFTS8tADhKcsbC2KBp8A4tx',
  minSponsoredAssetFee: 314,
}

const [tx] = await signer
  .sponsorship(data)
  .broadcast();

transfer

Creates Transfer transaction.

transfer(data: {
  recipient: 'string',
  amount: LONG,
  assetId: 'string',
  attachment: 'string',
  feeAssetId: 'string',
})

Parameters:

Parameter nameDefault valueDescription
recipient*Base58 encoded address or alias (with alias:T: prefix) of the recipient
amount*Amount of asset multiplied by 10^decimals. For example, decimals of KSS is 8, so the real amount is multipied by 10^8. { "KSS": 677728840 } means 6.77728840
assetIdKSSBase58 encoded ID of the asset to transfer. null or omitted field means KSS
attachmentOptional binary data base58 encoded. This field is often used to attach a comment to the transaction. The maximum data size is 140 bytes
feeAssetIdKSSBase58 encoded ID of the sponsored asset to pay the fee. See the Sponsored Fee article for more information. null or omitted field means KSS

* Required parameter.

See Common fields for other fields description.

Usage:

const crypto = require('@waves/ts-lib-crypto')

const data = {
  recipient: '3P8pGyzZL9AUuFs9YRYPDV3vm73T48ptZxs',
  amount: 10000,
  attachment: crypto.base58Encode(crypto.stringToBytes('sample message for recipient'))
}

const [tx] = await signer
  .transfer(data)
  .broadcast();

batch

Creates list of transactions.

batch([{
  type: number,
  ... // fields depending on the transaction type
}])

Parameters:

Parameter nameDefault valueDescription
type*Transaction type ID

* Required parameter.

Usage:

const [transfer, alias, issue] = await signer.batch([
  {
    type: 4,
    recipient: 'alias:T:merry',
    amount: 100000000
  },
  {
    type: 10,
    alias: 'send33'
  },
  {
    type: 3,
    name: 'SomeTokenName',
    description: 'Some Token Description',
    reissuable: false,
    quantity: 100,
    decimals: 1
  }
]).sign(); // Or broadcast

In this example, sign method returns array of signed transactions in the same order as they are defined in batch.

Others

broadcast

Sends transactions that are already signed to the blockchain.

broadcast(tx,[options])

Returns: Promise of node response. See the POST /transactions/broadcast method description of Node API.

Parameters:

Parameter nameDefault valueDescription
tx*Signed transaction or array of signed transactions
options.chainfalseType: boolean Send the next transaction only after the previous transaction is put in the blockchain and confirmed
options.confirmations-1Number of confirmations after that the Promise is resolved:less than 0 – Promise is resolved when the transaction is put in UTX pool0 – Promise is resolved when the block that contains the transaction is added to the blockchain1 – Promise is resolved when the next block is added to the blockchain and so on

* Required parameter.

Usage:

const [transfer1] = await signer.transfer({amount: 1, recipient: 'alias:T:merry'}).sign();
const [transfer2] = await signer.transfer({amount: 1, recipient: 'alias:T:merry'}).sign();

await signer.broadcast([transfer1, transfer2], {chain: true, confirmations: 2});

In this example:

  • transfer1 transaction is sent to the node and put in UTX pool.
  • Block with transfer1 and two more blocks are added to the blockchain.
  • transfer2 transaction is sent to the node and put in UTX pool.
  • Block with transfer2 and two more blocks are added to the blockchain.
  • Promise is resolved and you can notify user that his/her transactions are confirmed.

getNetworkByte

Obtains chain ID.

getNetworkByte();

Returns: Promise of chain ID.

Usage:

const chainId = signer.getNetworkByte();

setProvider

Specifies a Provider that is used to sign transactions. See Provider Interface to find out the provider requirements.

setProvider(provider);

Parameters:

Parameter nameDefault valueDescription
provider*Object that features Provider interface

* Required parameter.

Usage:

signer.setProvider(new Provider());

waitTxConfirm

Waits for the transaction to appear in the blockchain.

waitTxConfirm(tx, confirmations)

Parameters:

Parameter nameDefault valueDescription
tx*Transaction or array transactions that are sent to the blockchain
confirmations*Number of blocks added to the blockchain after the block that contains the transaction

* Required parameter.

Usage:

const [tx] = await signer
  .transfer({amount: 10000000, recipient: 'alias:T:merry'})
  .broadcast();

signer.waitTxConfirm(tx, 1).then((tx) => {
  // Tx have one confirmation
}});

Provider Interface

:warning: To ensure the security of user data, Provider should be based on iframe.

Provider should feature the following interface:

interface Provider {

    /**
     * Signer subscribes to login events in the Provider
     * When triggered, the Provider passes user data: address and public key 
     * For further unsubscribe Signer calls `off`
     */
    on(
        event: 'login',
        handler:({ address: string; publicKey: string }) => any 
    ) => Provider;

    /**
     * Signer subscribes to logout events in the Provider
     * For further unsubscribe Signer calls `off`
     */
    on( event: 'logout', handler:() => any) => Provider;

    /**
     * Signer subscribes to the first login event in the Provider
     * When triggered, the Provider passes user data: address and public key,
     *   then cancels the subscription
     */
    once(
        event: 'login',
        handler:({ address: string; publicKey: string }) => any 
    ) => Provider;

    /**
     * Signer subscribes to the first logout event in the Provider
     * When triggered, the Provider cancels the subscription
     */
    once( event: 'logout', handler:() => any) => Provider;

    /**
     * Signer unsubscribes from events previously subscribed to
     */
    off(
        event: 'login',
        handler:({ address: string; publicKey: string }) => any 
    ) => Provider;
    off( event: 'logout', handler:() => any) => Provider;

    /**
     * Sets connection to Waves node
     * @param options
     */
    connect(options: {NODE_URL: string, NETWORK_BYTE: number}): Promise<void>;

    /**
     * Authenticates user with their account
     */
    login(): Promise<{address: string, publicKey: string}>;

    /**
     * Logs user out
     */
    logout(): Promise<void>;

    /**
     * Signs custom message
     * @param data
     */
    signMessage(data: string | number): Promise<string>;

    /**
     * Signs typed data
     * @param data
     */
    signTypedData(data: Array<TypedData>): Promise<string>;

    /**
     * Signs transactions in array
     * Here SignedTx<T> is any transaction, T[] is an array of any transactions
     * @param list
     */
    sign<T extends SignerTx>(toSign: T[]): Promise<SignedTx<T>>;
    sign<T extends Array<SignerTx>>(toSign: T): Promise<SignedTx<T>>;
}

Error Codes

Error's classCodeTypeExample
SignerOptionsError1000validationInvalid signer options: NODE_URL, debug
SignerNetworkByteError1001networkCould not fetch network from {NODE_URL}: Failed to fetch
SignerAuthError1002authorizationCan't use method: getBalance. User must be logged in
SignerProviderConnectError1003networkCould not connect the Provider
SignerEnsureProviderError1004providerCan't use method: login. Provider instance is missing🛈 Possible reasons: the user is in Incognito mode or has disabled cookies
SignerProviderInterfaceError1005validationInvalid provider properties: connect
SignerProviderInternalError1006providerProvider internal error: {...}. This is not error of signer.
SignerApiArgumentsError1007validationValidation error for invoke transaction: {...}. Invalid arguments: senderPublicKey
SignerNetworkError1008networkNetwork Error