Wallet Policies API

Create

Currently in Levain, policies exist at the wallet level. There are multiple policies that can be created within a wallet, as described in the Using the Policy Engine guide:

Approval Quorum

All wallets created will initially have a default approval quorum, set to 1-of-1 Wallet Approvers.

The Approval Quorum is made up of two parts:

While you can approve through Levain App via the Requests tab within a wallet, you can also approve via the API using the approveTransactionRequest mutation.

MutationGraphQL
mutation AddPolicyWalletAccess($walletId: ID!, $memberId: ID!, $role: WalletMemberRole!) {
  addWalletAccess(
    input: {
      walletId: $walletId
      memberId: $memberId # memberId is ID of the user's membership in an org account
      role: $role
    }
  ) {
    # Returns WalletAccess object https://developer.levain.tech/products/graph/reference/objects#walletaccess
    walletId
    walletAccessId
    organizationMember {
      memberId # memberId is ID of the user's membership in an org account
      userId # userId is the ID of the user at the global level, across Levain
      user {
        firstName
        lastName
        email
      }
      role # Org-level role
    }
    role # Wallet-level role
  }
}
VariablesJSON
{
  "walletId": "abc12345-6789-0def-1234-567890abcdef",
  "memberId": "b2c3d4e5-6789-0abc-49ed-234567890abc",
  "role": "APPROVER"
}
ResponseJSON
{
  "data": {
    "addWalletAccess": {
      "walletId": "abc12345-6789-0def-1234-567890abcdef",
      "walletAccessId": "c3d4e5f6-2390-1abc-def1-234567890abc",
      "organizationMember": {
        "memberId": "b2c3d4e5-6789-0abc-49ed-234567890abc",
        "userId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
        "user": {
          "firstName": "Jia Jian",
          "lastName": "Goi",
          "email": "[email protected]"
        },
        "role": "OWNER"
      },
      "role": "APPROVER"
    }
  }
}

Signer

The Signer policy is used to specify who has the permissions to sign transactions from the wallet. We recommend using Levain App to add Signers to a wallet, and we will be sharing the API examples to add Signers to a wallet in the future.

Whitelist Address

Currently, Levain requires you to whitelist an address before it can be used to send transactions from the wallet. This is to prevent unauthorized transactions from being sent from the wallet. You can whitelist an address using the createPolicyWhitelist mutation.

MutationGraphQL
mutation CreatePolicyWhitelist($walletId: ID!, $address: String!, $label: String!) {
  createPolicyWhitelist(
    input: {
      walletId: $walletId
      address: $address
      label: $label # For example, label with your user's internal ID
    }
  ) {
    # Returns PolicyWhitelist https://developer.levain.tech/products/graph/reference/objects#policywhitelist
    policyId
    label
    address
    walletId
    createdAt
    updatedAt
  }
}
VariablesJSON
{
  "walletId": "2c3e5a17-326c-40c1-995a-dda9653fabf2",
  "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
  "label": "userId-9337483423940"
}
ResponseJSON
{
  "data": {
    "createPolicyWhitelist": {
      "policyId": "c3d4e5f6-2390-1abc-def1-234567890abc",
      "label": "userId-9337483423940",
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "walletId": "abc12345-6789-0def-1234-567890abcdef",
      "createdAt": "2024-02-23T05:13:28.022Z",
      "updatedAt": "2024-02-23T05:14:12.535Z"
    }
  }
}

Retrieve

Approval Quorum

You can retrieve the Approval Quorum for a wallet accessing the wallet query. You can specify any additional fields you want to retrieve from the WalletAccess object.

QueryGraphQL
query GetWalletApprovalQuorum($walletId: ID!) {
  wallet(walletId: $walletId) {
    walletId
    name
    mainAddress
    approvers {
      edges {
        node {
          walletAccessId
          memberId
          organizationMember {
            memberId
            userId
            user {
              firstName
              lastName
              email
            }
            role # Org-level role
          }
          role # Wallet-level role
        }
      }
      totalCount # Total number of Wallet Approvers
    }
    policy {
      policyId
      approvalThreshold # Approval threshold required
    }
  }
}
VariablesJSON
{
  "walletId": "2c3e5a17-326c-40c1-995a-dda9653fabf2"
}
ResponseJSON
{
  "data": {
    "wallet": {
      "walletId": "2c3e5a17-326c-40c1-995a-dda9653fabf2",
      "name": "Arbitrum Ops Withdrawal Wallet",
      "mainAddress": "0x8061B2CD8ff2bcB67831653C5cCBA28768291546",
      "approvers": {
        "edges": [
          {
            "node": {
              "walletAccessId": "b2c3d4e5-6789-0abc-49ed-234567890abc",
              "memberId": "c3d4e5f6-2390-1abc-def1-234567890abc",
              "organizationMember": {
                "memberId": "47d0f7cd-cec5-4840-9c9d-557e3c959a16",
                "userId": "c01dad5a-918c-4abc-b479-9c16af83050c",
                "user": {
                  "firstName": "Jia Jian",
                  "lastName": "Goi",
                  "email": "jiajian@@example.com"
                },
                "role": "OWNER"
              },
              "role": "APPROVER"
            }
          }
        ],
        "totalCount": 1
      },
      "policy": {
        "policyId": "c3c9ac05-36ef-40d4-84ab-bac121b20441",
        "approvalThreshold": 1
      }
    }
  }
}

Current Viewer's Signing Key

From Levain v24, we introduced the concept of a viewerPassword object, which is the user signing key, encrypted with the password specified by the current viewer (i.e. the user making the query), during Add Signer process. This feature allows users to share the wallet with other users without sharing the user signing key directly, and each signer can have their own password to decrypt the user signing key.

You can retrieve the viewerPassword object (i.e. the user signing key, encrypted with the current query viewer's specified password) from the following query. If you do not have any signing key for the wallet, the the first wallet key password is returned, i.e. the original user signing key that was encrypted with a password specified by the original wallet creator.

You can specify any additional fields you want to retrieve from the WalletAccess object.

QueryGraphQL
query GetCurrentViewerPassword($walletId: ID!) {
  wallet(walletId: $walletId) {
    walletId
    name
    mainAddress
    signingKey {
      viewerPassword {
        walletKeyId
        walletAccessId
        walletKeyPasswordId
        cipherBlob {
          cipher
          keyDerivation
          keyHasher
          iter
          salt
          iv
          cipherText
          hash
        }
        status
      }
      createdAt
    }
  }
}
VariablesJSON
{
  "walletId": "2c3e5a17-326c-40c1-995a-dda9653fabf2"
}
ResponseJSON
{
  "data": {
    "wallet": {
      "walletId": "2c3e5a17-326c-40c1-995a-dda9653fabf2",
      "name": "1_Arbitrum Ops Wallet (v13.0.0)",
      "mainAddress": "0x8061B2CD8ff2bcB67831653C5cCBA28768291546",
      "signingKey": {
        "viewerPassword": {
          "walletKeyId": "0544ab6c-6c20-4d5d-9f9a-2ddfe04a51c1",
          "walletAccessId": null,
          "walletKeyPasswordId": "51766ce8-2f9a-45b2-b2f6-b60904dd1af1",
          "cipherBlob": {
            "cipher": "AES_256_CBC",
            "keyDerivation": "pbkdf2",
            "keyHasher": "sha256",
            "iter": 10000,
            "salt": "<REDACTED>",
            "iv": "<REDACTED>",
            "cipherText": "<REDACTED>",
            "hash": "<REDACTED>"
          },
          "status": "ACTIVE"
        },
        "createdAt": "2023-10-05T04:02:53.225Z"
      }
    }
  }
}