Approvals via Smart Contract
When configured for Smart Contract mode for approvals, the Virtual Signer will invoke the functions of
a smart contract to approve or reject requests or abstain from a decision.
ApprovalNodeRPCAddress in the Virtual Signer configuration refers to the RPC URL of an Ethereum or io.network node.
ApprovalSmartContractAddress in configuration refers to the address of the smart contract that implements the approval logic.
The smart contract must have two functions with the following signatures:
function approvedKeygenReshare(string memory encodedKeygenReshare) public returns (uint8)
function approvedSignOp(string memory encodedSignOp) public returns (uint8)
The functions are invoked by the Virtual Signer with ABI encoded tuples that represent a keygen or reshare request, or a sign operation request. The functions return 0 to approve the request, 1 to reject it or 2 to abstain from a decision.
Sample Smart Contract
Considering that the Virtual Signer encodes request data using ABI, it is important for the smart contract to have structs that match the type and data encodings performed by the Virtual Signer. These are the structs that the smart contract must have:
struct KeygenRequestSignerSC {
uint Weight;
string Uiid;
string Id;
}
struct DeviceAddedSC {
uint Weight;
string Uiid;
string Id;
}
struct DeviceRemovedSC {
uint Weight;
string Uiid;
string Id;
}
struct DevicePowerChangedSC {
uint NewWeight;
uint OldWeight;
string Uiid;
string Id;
}
struct KeygenReshareRequestSC {
string Typename;
string Id;
uint ExpiresAt;
string OrganisationId;
string ReshareRequestStatus;
string VaultId;
string VaultName;
uint ReshareNonce;
uint NewThreshold;
uint OldThreshold;
KeygenRequestSignerSC[] NewSigners;
DeviceAddedSC[] DevicesAdded;
DeviceRemovedSC[] DevicesRemoved;
DevicePowerChangedSC[] DevicesPowerChanged;
}
struct NodeOpSignRequestSC {
string Typename ;
string Id ;
string OrganisationId ;
string OperationSignStatus ;
string VaultId ;
string VaultName ;
string Data ;
string [] DataArray ;
string DerivationPath ;
string CoseAlgorithmType ;
string CoseAlgorithmValue ;
string ContentType ;
uint ChainId ;
}The smart contract can decode the data as follows:
function decodeSignOp(bytes memory data) public pure returns (string memory, uint) {
NodeOpSignRequestSC memory sopReq = abi.decode(data, (NodeOpSignRequestSC));
return (sopReq.Data, sopReq.ChainId);
}
After ABI decoding, the smart contract can approve or reject the request or abstain by referring to the decoded data. For example:
uint8 public constant APPROVE = 0;
uint8 public constant REJECT = 1;
uint8 public constant ABSTAIN = 2;
bytes32 public constant REJECTSTRING = keccak256(abi.encodePacked("do fail the test"));
bytes32 public constant ABSTAINSTRING = keccak256(abi.encodePacked("do abstain"));
function approvedSignOp(bytes memory encodedSignOp) public returns (uint8){
(string memory data, uint chainId) = decodeSignOp(encodedSignOp);
bytes32 dataBytes = keccak256(abi.encodePacked(data));
if (dataBytes == REJECTSTRING) {
return REJECT;
} else if (dataBytes == ABSTAINSTRING) {
return ABSTAIN;
}
return APPROVE;
}Sample Approval Smart Contract
We present below a sample approval smart contract. The functions that approve or reject requests are only illustrative and should be changed according to real business cases.
pragma solidity ^0.8.24;
/**
* @title Approval
*/
contract Approval {
uint8 public constant APPROVE = 0;
uint8 public constant REJECT = 1;
uint8 public constant ABSTAIN = 2;
bytes32 public constant REJECTAMOUNT = keccak256(abi.encodePacked("0.00666"));
bytes32 public constant ABSTAINAMOUNT = keccak256(abi.encodePacked("0.00999"));
bytes32 public constant REJECTSTRING = keccak256(abi.encodePacked("do fail the test"));
bytes32 public constant ABSTAINSTRING = keccak256(abi.encodePacked("do abstain"));
struct KeygenRequestSignerSC {
uint Weight;
string Uiid;
string Id;
}
struct DeviceAddedSC {
uint Weight;
string Uiid;
string Id;
}
struct DeviceRemovedSC {
uint Weight;
string Uiid;
string Id;
}
struct DevicePowerChangedSC {
uint NewWeight;
uint OldWeight;
string Uiid;
string Id;
}
struct KeygenReshareRequestSC {
string Typename;
string Id;
uint ExpiresAt;
string OrganisationId;
string ReshareRequestStatus;
string VaultId;
string VaultName;
uint ReshareNonce;
uint NewThreshold;
uint OldThreshold;
KeygenRequestSignerSC[] NewSigners;
DeviceAddedSC[] DevicesAdded;
DeviceRemovedSC[] DevicesRemoved;
DevicePowerChangedSC[] DevicesPowerChanged;
}
struct NodeOpSignRequestSC {
string Typename ;
string Id ;
string OrganisationId ;
string OperationSignStatus ;
string VaultId ;
string VaultName ;
string Data ;
string [] DataArray ;
string DerivationPath ;
string CoseAlgorithmType ;
string CoseAlgorithmValue ;
string ContentType ;
uint ChainId ;
}
function decodeKeygenReshare(bytes memory data) public pure returns (string memory, uint) {
KeygenReshareRequestSC memory kgReq = abi.decode(data, (KeygenReshareRequestSC));
return (kgReq.Id, kgReq.NewThreshold);
}
function decodeSignOp(bytes memory data) public pure returns (string memory, uint) {
NodeOpSignRequestSC memory sopReq = abi.decode(data, (NodeOpSignRequestSC));
return (sopReq.Data, sopReq.ChainId);
}
function approvedKeygenReshare(bytes memory encodedKeygenReshare) public pure returns (uint8){
(string memory id, uint t) = decodeKeygenReshare(encodedKeygenReshare);
if (t == 9) {
return ABSTAIN;
} else if (t >= 10) {
return REJECT;
}
return APPROVE;
}
function approvedSignOp(bytes memory encodedSignOp) public pure returns (uint8){
(string memory data, uint chainId) = decodeSignOp(encodedSignOp);
bytes32 dataBytes = keccak256(abi.encodePacked(data));
if (dataBytes == REJECTSTRING) {
return REJECT;
} else if (dataBytes == ABSTAINSTRING) {
return ABSTAIN;
}
return APPROVE;
}
}Updated 10 days ago