The Sylo Action Permissions pallet allows accounts to manage transact permissions. A transact permission enables the grantee to execute extrinsics on behalf of the grantor.

Transact Permission

A transact permission record is stored on-chain, keyed by the account id of the grantor, and the account id of the grantee. It allows the grantee to call the transact extrinsic, which execute the underlying extrinsic on behalf of the grantor. The record contains the following fields:

  • spender: The account that is pays for the extrinsic fees when transact is called. This is either the GRANTOR or the GRANTEE.

  • spending_balance: When the spender is the GRANTOR, the grantor can specify a spending balance. This acts as a limit on the maximum amount of funds that the grantee can use to call transact on behalf of the grantor. This value is deducted by the extrinsic fee when transact is called.

  • allowed_calls: A list of CallId that the grantee can use in transact. This value must be specified for all of the extrinsics that the grantor wishes to permit. A call id is a tuple of the pallet name and the extrinsic name. Example: ("nft", "transfer"). Wildcard values can be used for both the pallet and extrinsic names, e.g. ("*", "*") will allow all extrinsics to be called.

    A set of pallets are blacklisted from being used in transact. This includes the following pallets:

    • Sudo
    • Proxy
    • Futurepass
    • SyloActionPermissions (prevents grantee from modifying the permission record itself)
  • expiry: An optional expiry block for the permission.

Payment

The extrinsics for managing the transact permissions (grant, revoke, update, accept) are paid using the SYLO token (asset id 2148). It is not possible to use the fee proxy to modify the fee token for these extrinsics.

The transact extrinsic itself is paid using the regular fee payment mechanism.

Calls

grantTransactPermission

Grant a transact permission to another account.

Namespace

api.tx.syloActionPermissions.grantTransactPermission

Type

function grantTransactPermission(
  /// The account to grant the permission to
  grantee: AccountId,
  /// The account that will pay for the extrinsic fees
  spender: Spender,
  /// The maximum amount of funds that the grantee can use when calling `transact`
  spending_balance: Option<Balance>,
  /// The list of allowed calls,
  allowed_calls: Vec<(Vec<Bytes>, Vec<Bytes>)>,
  /// An optional expiry block for the permission
  expiry: Option<BlockNumber>
)

updateTransactPermission

Update an existing transact permission record.

This takes in a set of optional values, which will update the existing record if the value is Some.

Namespace

api.tx.syloActionPermissions.updateTransactPermission

Type

function updateTransactPermission(
  /// The grantee to update the permission for
  grantee: AccountId,

  /// The account that will pay for the extrinsic fees
  spender: Option(Spender),

  /// The maximum amount of funds that the grantee can use when calling `transact`
  spending_balance: Option<Option<Balance>>,

  /// The list of allowed calls,
  allowed_calls: Option<Vec<(Vec<Bytes>, Vec<Bytes>)>>,

  /// An optional expiry block for the permission
  expiry: Option<Option<BlockNumber>>
)

revokeTransactPermission

Revoke a previously granted transact permission.

Namespace

api.tx.syloActionPermissions.revokeTransactPermission

Type

function revokeTransactPermission(
  /// The grantee to revoke the permission for
  grantee: AccountId
)

acceptTransactPermission

Accept a transact permission that has been granted to the caller. Unlike grantTransactPermission, this call is intended to be used by the grantee.

This call is primarily for the use case where the grantor has generated a new account, but does not have the means to execute the grantTransactPermission themselves. For example, not having any funds associated with the account.

The grantor can instead sign a token that includes the permission details, as well as the grantee’s account id. The grantee can then supply the token and token signature to this call to create the accompanying permission record.

Updating the on-chain permission record can be done by calling this extrinsic again with the updated values and a new token signature.

Token Signature

The grantor must sign an encoded transact permission token. The token structure includes the following fields:

type TransactPermissionToken = {
  // Account id of the grantee
  grantee: AccountId,

  // Indicates that the futurepass account of the permission grantor
	// should be used, instead of the account recovered from the signature.
  use_futurepass: Option<AccountId>,

  // The account that will pay for the extrinsic fees
  spender: Spender,

  // The maximum amount of funds that the grantee can use when calling `transact`
  spending_balance: Option<Balance>,

  // The list of allowed calls,
  allowed_calls: Vec<(Vec<Bytes>, Vec<Bytes>)>,

  // An optional expiry block for the permission
  expiry: Option<BlockNumber>,

  // A randomly generated 32 byte nonce used to prevent replays
  nonce: Bytes
}

The type registry for PalletSyloActionPermissionsTransactPermissionToken can be used to encode the token.

import { u8aToHex } from "@polkadot/util";
import { utils as ethersUtils } from "ethers";

const SPENDER_TYPE = {
  Grantor: "GRANTOR",
  Grantee: "GRANTEE",
};

const permissionToken = {
  grantee: grantee.address,
  use_futurepass: false,
  spender: SPENDER_TYPE.Grantee,
  spending_balance: null,
  allowed_calls: [["*", "*"]],
  expiry: null,
  nonce: u8aToHex(ethersUtils.randomBytes(32)),
};

const serializedToken = api.registry
  .createType(
    "PalletSyloActionPermissionsTransactPermissionToken",
    permissionToken
  )
  .toU8a();

Signing

A signature can either be in the form of an EIP191 signature, or an XRPL signature.

import { KeyringPair } from "@polkadot/keyring/types";
import { hexToU8a } from "@polkadot/util";

const grantor = keyring.addFromSeed(hexToU8a(`0xPrivateKey`));

// Sign the serialized token using EIP191
const signature = await grantor.signMessage(serializedToken);

const eip191Sig = { EIP191: signature };
import { encode, encodeForSigning } from "ripple-binary-codec";
import { computePublicKey } from "ethers/lib/utils";
import { KeyringPair } from "@polkadot/keyring/types";
import { hexToU8a } from "@polkadot/util";

const GRANTOR_PRIVATE_KEY = "0xPrivateKey";

const grantor = keyring.addFromSeed(hexToU8a(GRANTOR_PRIVATE_KEY));

// compute ecdsa public key
const publicKey = computePublicKey(grantor.publicKey, true);

// create xaman tx object
const xamanJsonTx = {
  AccountTxnID:
    "16969036626990000000000000000000F236FD752B5E4C84810AB3D41A3C2580",
  SigningPubKey: publicKey.slice(2),
  Account: deriveAddress(publicKey.slice(2)),
  Memos: [
    {
      Memo: {
        MemoType: stringToHex("extrinsic"),
        MemoData: u8aToHex(serializedToken).substring(2),
      },
    },
  ],
};

// Sign XRPL transaction
const encodedSigningMessage = encodeForSigning(xamanJsonTx);
const signature = sign(encodedSigningMessage, GRANTOR_PRIVATE_KEY.slice(2));

const xrplSig = {
  XRPL: {
    encodedMsg: `0x${encode(xamanJsonTx)}`,
    signature: `0x${signature}`,
  },
};

Namespace

Type

acceptTransactPermission(
  /// The token that contains the permission details
  token: TransactPermissionToken,

  /// The signature of the token, either EIP191 or XRPL
  signature: TransactPermissionTokenSignature
);

Storage

TransactPermissions

Map from grantor and grantee to a transact permission record.

Namespace

api.query.syloActionPermissions.transactPermissions

Type

type TransactPermission = {
  spender: GRANTOR | GRANTEE,
  spending_balance: Option<Balance>,
  allowed_calls: Vec<(CallId)>,
  block: BlockNumber,
  expiry: Option<BlockNumber>
}

function TransactPermissions(
  grantor: AccountId,
  grantee: AccountId
): Option<TransactPermission>

Events

TransactPermissionGranted

An account has been granted a transact permission.

Namespace

api.events.syloActionPermissions.TransactPermissionGranted

Type

type TransactPermissionGranted = {
  grantor: AccountId,
  grantee: AccountId,
  spender: Spender,
  spending_balance: Option<Balance>,
  allowed_calls: Vec<CallId>,
  expiry: Option<BlockNumber>,
}

TransactPermissionUpdated

An account has updated a transact permission.

Namespace

api.events.syloActionPermissions.TransactPermissionUpdated

Type

type TransactPermissionUpdated = {
  grantor: AccountId,
  grantee: AccountId,
  spender: Spender,
  spending_balance: Option<Balance>,
  allowed_calls: Vec<CallId>,
  expiry: Option<BlockNumber>,
}

TransactPermissionRevoked

An account has revoked a transact permission.

Namespace

api.events.syloActionPermissions.TransactPermissionRevoked

Type

type TransactPermissionRevoked = {
  grantor: AccountId,
  grantee: AccountId,
}

TransactPermissionAccepted

An account has accepted a transact permission.

Namespace

api.events.syloActionPermissions.TransactPermissionAccepted

Type

type TransactPermissionAccepted = {
  grantor: AccountId,
  grantee: AccountId,
}

TransactPermissionExecuted

An account has executed an extrinsic on behalf of another account using a transact permission.

Namespace

api.events.syloActionPermissions.TransactPermissionExecuted

Type

type TransactPermissionExecuted = {
  grantor: AccountId,
  grantee: AccountId,
}

Errors

PermissionNotGranted

The permission does not exist or has not been granted.

Namespace

api.errors.syloActionPermissions.PermissionNotGranted

NotAuthorizedCall

The call is not authorized under the granted permission.

Namespace

api.errors.syloActionPermissions.NotAuthorizedCall

PermissionExpired

The permission has expired and is no longer valid.

Namespace

api.errors.syloActionPermissions.PermissionExpired

InvalidExpiry

The provided expiry block is in the past.

Namespace

api.errors.syloActionPermissions.InvalidExpiry

PermissionAlreadyExists

A permission already exists and has not yet expired.

Namespace

api.errors.syloActionPermissions.PermissionAlreadyExists

InvalidSpendingBalance

The specified spending balance is not allowed.

Namespace

api.errors.syloActionPermissions.InvalidSpendingBalance

InvalidTokenSignature

The provided token signature is invalid or cannot be verified.

Namespace

api.errors.syloActionPermissions.InvalidTokenSignature

GranteeDoesNotMatch

The grantee in the token does not match the caller.

Namespace

api.errors.syloActionPermissions.GranteeDoesNotMatch

NonceAlreadyUsed

The nonce provided in the token has already been used.

Namespace

api.errors.syloActionPermissions.NonceAlreadyUsed

InvalidFuturepassInToken

The futurepass in the token is not owned by the grantor.

Namespace

api.errors.syloActionPermissions.InvalidFuturepassInToken

InsufficientSpendingBalance

The spending balance is insufficient to cover the transaction fee.

Namespace

api.errors.syloActionPermissions.InsufficientSpendingBalance