Skip to content

MsgUpdateUserApprovals replaces a user's outgoing and incoming approvals, auto-approve flags, and user permissions in one collection.

Updates the signer's own approval settings for one collection. Each field has an update flag; only flagged fields change.

Example

bash
bb tx tokenization update-user-approved-transfers ./user-approvals.json --from bob --chain-id bitbadges-1
ts
import { BitBadgesSigningClient, GenericCosmosAdapter, MsgUpdateUserApprovals } from 'bitbadges';

const adapter = await GenericCosmosAdapter.fromMnemonic(process.env.MNEMONIC!, 'bitbadges-1');
const client = new BitBadgesSigningClient({ adapter, network: 'mainnet' });

// bob lets alice move his token 1 for the next 30 days. Nothing else changes.
const msg = new MsgUpdateUserApprovals({
  creator: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
  collectionId: 1n,
  updateOutgoingApprovals: true,
  outgoingApprovals: [
    {
      toListId: 'bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d',
      initiatedByListId: 'bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d',
      transferTimes: [{ start: 1788739200000n, end: 1791331200000n }],
      tokenIds: [{ start: 1n, end: 1n }],
      ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
      uri: '',
      customData: '',
      approvalId: 'let-alice-move-token-1',
      approvalCriteria: {
        merkleChallenges: [],
        predeterminedBalances: {
        },
        approvalAmounts: {
        },
        maxNumTransfers: {
        },
        coinTransfers: [],
        requireToEqualsInitiatedBy: false,
        requireToDoesNotEqualInitiatedBy: false,
        autoDeletionOptions: {
        },
        mustOwnTokens: [],
        dynamicStoreChallenges: [],
        ethSignatureChallenges: [],
        recipientChecks: {
        },
        initiatorChecks: {
        },
        altTimeChecks: {
        },
        mustPrioritize: false,
        votingChallenges: [],
        evmQueryChallenges: []
      },
      version: 0n
    }
  ],
  updateIncomingApprovals: false,
  incomingApprovals: [],
  updateAutoApproveSelfInitiatedOutgoingTransfers: false,
  autoApproveSelfInitiatedOutgoingTransfers: true,
  updateAutoApproveSelfInitiatedIncomingTransfers: false,
  autoApproveSelfInitiatedIncomingTransfers: true,
  updateAutoApproveAllIncomingTransfers: false,
  autoApproveAllIncomingTransfers: true,
  updateUserPermissions: false,
  userPermissions: {
  }
});

const result = await client.signAndBroadcast([msg]);
console.log(result.txHash, result.success);
import { BitBadgesSigningClient, GenericCosmosAdapter, MsgUpdateUserApprovals } from 'bitbadges';

const adapter = await GenericCosmosAdapter.fromMnemonic(process.env.MNEMONIC!, 'bitbadges-1');
const client = new BitBadgesSigningClient({ adapter, network: 'mainnet' });

// bob lets alice move his token 1 for the next 30 days. Nothing else changes.
const msg = new MsgUpdateUserApprovals({
  creator: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
  collectionId: 1n,
  updateOutgoingApprovals: true,
  outgoingApprovals: [
    {
      toListId: 'bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d',
      initiatedByListId: 'bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d',
      transferTimes: [{ start: 1788739200000n, end: 1791331200000n }],
      tokenIds: [{ start: 1n, end: 1n }],
      ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
      uri: '',
      customData: '',
      approvalId: 'let-alice-move-token-1',
      approvalCriteria: {
        merkleChallenges: [],
        predeterminedBalances: {
          manualBalances: [],
          incrementedBalances: {
            startBalances: [],
            incrementTokenIdsBy: 0n,
            incrementOwnershipTimesBy: 0n,
            durationFromTimestamp: 0n,
            allowOverrideTimestamp: false,
            recurringOwnershipTimes: { startTime: 0n, intervalLength: 0n, chargePeriodLength: 0n },
            allowOverrideWithAnyValidToken: false,
            allowAmountScaling: false,
            maxScalingMultiplier: 0n
          },
          orderCalculationMethod: {
            useOverallNumTransfers: false,
            usePerToAddressNumTransfers: false,
            usePerFromAddressNumTransfers: false,
            usePerInitiatedByAddressNumTransfers: false,
            useMerkleChallengeLeafIndex: false,
            challengeTrackerId: ''
          }
        },
        approvalAmounts: {
          overallApprovalAmount: 0n,
          perToAddressApprovalAmount: 0n,
          perFromAddressApprovalAmount: 0n,
          perInitiatedByAddressApprovalAmount: 0n,
          amountTrackerId: '',
          resetTimeIntervals: { startTime: 0n, intervalLength: 0n }
        },
        maxNumTransfers: {
          overallMaxNumTransfers: 0n,
          perToAddressMaxNumTransfers: 0n,
          perFromAddressMaxNumTransfers: 0n,
          perInitiatedByAddressMaxNumTransfers: 0n,
          amountTrackerId: '',
          resetTimeIntervals: { startTime: 0n, intervalLength: 0n }
        },
        coinTransfers: [],
        requireToEqualsInitiatedBy: false,
        requireToDoesNotEqualInitiatedBy: false,
        autoDeletionOptions: {
          afterOneUse: false,
          afterOverallMaxNumTransfers: false,
          allowCounterpartyPurge: false,
          allowPurgeIfExpired: false
        },
        mustOwnTokens: [],
        dynamicStoreChallenges: [],
        ethSignatureChallenges: [],
        recipientChecks: {
          mustBeEvmContract: false,
          mustNotBeEvmContract: false,
          mustBeLiquidityPool: false,
          mustNotBeLiquidityPool: false
        },
        initiatorChecks: {
          mustBeEvmContract: false,
          mustNotBeEvmContract: false,
          mustBeLiquidityPool: false,
          mustNotBeLiquidityPool: false
        },
        altTimeChecks: {
          offlineHours: [],
          offlineDays: [],
          offlineMonths: [],
          offlineDaysOfMonth: [],
          offlineWeeksOfYear: [],
          timezoneOffsetMinutes: 0n,
          timezoneOffsetNegative: false
        },
        mustPrioritize: false,
        votingChallenges: [],
        evmQueryChallenges: []
      },
      version: 0n
    }
  ],
  updateIncomingApprovals: false,
  incomingApprovals: [],
  updateAutoApproveSelfInitiatedOutgoingTransfers: false,
  autoApproveSelfInitiatedOutgoingTransfers: true,
  updateAutoApproveSelfInitiatedIncomingTransfers: false,
  autoApproveSelfInitiatedIncomingTransfers: true,
  updateAutoApproveAllIncomingTransfers: false,
  autoApproveAllIncomingTransfers: true,
  updateUserPermissions: false,
  userPermissions: {
    canUpdateOutgoingApprovals: [],
    canUpdateIncomingApprovals: [],
    canUpdateAutoApproveSelfInitiatedOutgoingTransfers: [],
    canUpdateAutoApproveSelfInitiatedIncomingTransfers: [],
    canUpdateAutoApproveAllIncomingTransfers: []
  }
});

const result = await client.signAndBroadcast([msg]);
console.log(result.txHash, result.success);
json
{
  "creator": "bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue",
  "collectionId": "1",
  "updateOutgoingApprovals": true,
  "outgoingApprovals": [
    {
      "toListId": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
      "initiatedByListId": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
      "transferTimes": [{ "start": "1788739200000", "end": "1791331200000" }],
      "tokenIds": [{ "start": "1", "end": "1" }],
      "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
      "approvalId": "let-alice-move-token-1",
      "approvalCriteria": {
        "predeterminedBalances": {
          "incrementedBalances": {
            "recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
          }
        },
        "approvalAmounts": {
          "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
        },
        "maxNumTransfers": {
          "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
        }
      }
    }
  ],
  "autoApproveSelfInitiatedOutgoingTransfers": true,
  "autoApproveSelfInitiatedIncomingTransfers": true,
  "autoApproveAllIncomingTransfers": true
}
{
  "creator": "bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue",
  "collectionId": "1",
  "updateOutgoingApprovals": true,
  "outgoingApprovals": [
    {
      "toListId": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
      "initiatedByListId": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
      "transferTimes": [{ "start": "1788739200000", "end": "1791331200000" }],
      "tokenIds": [{ "start": "1", "end": "1" }],
      "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
      "uri": "",
      "customData": "",
      "approvalId": "let-alice-move-token-1",
      "approvalCriteria": {
        "merkleChallenges": [],
        "predeterminedBalances": {
          "manualBalances": [],
          "incrementedBalances": {
            "startBalances": [],
            "incrementTokenIdsBy": "0",
            "incrementOwnershipTimesBy": "0",
            "durationFromTimestamp": "0",
            "allowOverrideTimestamp": false,
            "recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" },
            "allowOverrideWithAnyValidToken": false,
            "allowAmountScaling": false,
            "maxScalingMultiplier": "0"
          },
          "orderCalculationMethod": {
            "useOverallNumTransfers": false,
            "usePerToAddressNumTransfers": false,
            "usePerFromAddressNumTransfers": false,
            "usePerInitiatedByAddressNumTransfers": false,
            "useMerkleChallengeLeafIndex": false,
            "challengeTrackerId": ""
          }
        },
        "approvalAmounts": {
          "overallApprovalAmount": "0",
          "perToAddressApprovalAmount": "0",
          "perFromAddressApprovalAmount": "0",
          "perInitiatedByAddressApprovalAmount": "0",
          "amountTrackerId": "",
          "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
        },
        "maxNumTransfers": {
          "overallMaxNumTransfers": "0",
          "perToAddressMaxNumTransfers": "0",
          "perFromAddressMaxNumTransfers": "0",
          "perInitiatedByAddressMaxNumTransfers": "0",
          "amountTrackerId": "",
          "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
        },
        "coinTransfers": [],
        "requireToEqualsInitiatedBy": false,
        "requireToDoesNotEqualInitiatedBy": false,
        "autoDeletionOptions": {
          "afterOneUse": false,
          "afterOverallMaxNumTransfers": false,
          "allowCounterpartyPurge": false,
          "allowPurgeIfExpired": false
        },
        "mustOwnTokens": [],
        "dynamicStoreChallenges": [],
        "ethSignatureChallenges": [],
        "recipientChecks": {
          "mustBeEvmContract": false,
          "mustNotBeEvmContract": false,
          "mustBeLiquidityPool": false,
          "mustNotBeLiquidityPool": false
        },
        "initiatorChecks": {
          "mustBeEvmContract": false,
          "mustNotBeEvmContract": false,
          "mustBeLiquidityPool": false,
          "mustNotBeLiquidityPool": false
        },
        "altTimeChecks": {
          "offlineHours": [],
          "offlineDays": [],
          "offlineMonths": [],
          "offlineDaysOfMonth": [],
          "offlineWeeksOfYear": [],
          "timezoneOffsetMinutes": "0",
          "timezoneOffsetNegative": false
        },
        "mustPrioritize": false,
        "votingChallenges": [],
        "evmQueryChallenges": []
      },
      "version": "0"
    }
  ],
  "updateIncomingApprovals": false,
  "incomingApprovals": [],
  "updateAutoApproveSelfInitiatedOutgoingTransfers": false,
  "autoApproveSelfInitiatedOutgoingTransfers": true,
  "updateAutoApproveSelfInitiatedIncomingTransfers": false,
  "autoApproveSelfInitiatedIncomingTransfers": true,
  "updateAutoApproveAllIncomingTransfers": false,
  "autoApproveAllIncomingTransfers": true,
  "updateUserPermissions": false,
  "userPermissions": {
    "canUpdateOutgoingApprovals": [],
    "canUpdateIncomingApprovals": [],
    "canUpdateAutoApproveSelfInitiatedOutgoingTransfers": [],
    "canUpdateAutoApproveSelfInitiatedIncomingTransfers": [],
    "canUpdateAutoApproveAllIncomingTransfers": []
  }
}

Fields

When an update* flag is false the paired value is ignored, so placeholder data is safe.

FieldTypeRequiredDescription
creatorstringyesSigner. Users can only update their own approvals.
collectionIdUintyesCollection. "0" resolves to the most recently created collection.
updateOutgoingApprovals, outgoingApprovalsbool, UserOutgoingApproval[]noFull replacement list of outgoing approvals.
updateIncomingApprovals, incomingApprovalsbool, UserIncomingApproval[]noFull replacement list of incoming approvals.
updateAutoApproveSelfInitiatedOutgoingTransfers, autoApproveSelfInitiatedOutgoingTransfersbool, boolnoApprove transfers where from == initiatedBy.
updateAutoApproveSelfInitiatedIncomingTransfers, autoApproveSelfInitiatedIncomingTransfersbool, boolnoApprove transfers where to == initiatedBy.
updateAutoApproveAllIncomingTransfers, autoApproveAllIncomingTransfersbool, boolnoApprove every incoming transfer. Shorthand for an accept-all incoming approval.
updateUserPermissions, userPermissionsbool, UserPermissionsnoPermissions that guard future updates to the fields above.

Response

FieldTypeDescription
incomingChangesApprovalChange[]Incoming approvals created, edited, or deleted.
outgoingChangesApprovalChange[]Outgoing approvals created, edited, or deleted.
reviewItemsstring[]Advisory notes, including a summary of the changes.

Behavior

  • Fails with ErrCollectionNotExists for an unknown collection and ErrCollectionIsArchived for an archived one.
  • If the user has no stored balance record, the collection's defaultBalances are applied first. In that case every approval keeps version 0.
  • Otherwise a new or changed approval gets an incremented version; unchanged approvals keep theirs. Versions are what prioritizedApprovals in MsgTransferTokens pin.
  • Approvals that cannot be auto-scanned get mustPrioritize forced on.
  • Outgoing and incoming lists are validated against canUpdateOutgoingApprovals and canUpdateIncomingApprovals.
  • Auto-approve flags are checked against their permission only when the value actually changes.
  • New userPermissions cannot re-open a permanently forbidden time.
  • User permissions are almost always left permanently allowed. Customize them only when you need to lock a user's own approvals, for example a vault address.

Edit this page on GitHub