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