= {
+ editAccount: vi.fn().mockImplementation(() => Promise.resolve({} as Account)),
+ };
+
+ return {
+ StationService: vi.fn(() => mock),
+ };
+});
+
+const mockAssets: Asset[] = [
+ {
+ id: '1',
+ blockchain: 'icp',
+ decimals: 8,
+ metadata: [],
+ name: 'Test',
+ symbol: 'TEST',
+ standards: [BlockchainStandard.Native],
+ },
+
+ {
+ id: '2',
+ blockchain: 'icp',
+ decimals: 8,
+ metadata: [],
+ name: 'Test2',
+ symbol: 'TEST2',
+ standards: [BlockchainStandard.ICRC1],
+ },
+];
+
+const mockAccount: Account = {
+ id: '1',
+ assets: [
+ {
+ asset_id: mockAssets[0].id,
+ balance: [],
+ },
+ ],
+ addresses: [],
+ configs_request_policy: [],
+ metadata: [],
+ last_modification_timestamp: '2021-09-01T00:00:00Z',
+ name: 'Test',
+ transfer_request_policy: [],
+};
+
+describe('AddAccountAssetDialog', () => {
+ it('renders correctly', () => {
+ const wrapper = mount(AddAccountAssetDialog, {
+ props: {
+ account: mockAccount,
+
+ open: true,
+ attach: true,
+ },
+ });
+
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('edits the account when submitted', async () => {
+ const wrapper = mount(AddAccountAssetDialog, {
+ props: {
+ account: { ...mockAccount },
+ open: true,
+ attach: true,
+ },
+ });
+
+ const station = useStationStore();
+ station.configuration.details.supported_assets = mockAssets;
+
+ const submitBtn = wrapper.find('button[data-test-id="add-asset-dialog-save-button"]');
+
+ const tokenField = wrapper.findComponent(TokenAutocomplete);
+
+ tokenField.vm.$emit('update:modelValue', [mockAssets[1].id]);
+
+ await wrapper.vm.$nextTick();
+ await flushPromises();
+
+ await submitBtn.trigger('click');
+
+ await wrapper.vm.$nextTick();
+ await flushPromises();
+
+ // check if editAccount was called with the correct asset
+ expect(services().station.editAccount).toHaveBeenCalledWith(
+ expect.objectContaining({
+ change_assets: [
+ {
+ Change: {
+ add_assets: [mockAssets[1].id],
+ remove_assets: [],
+ },
+ },
+ ],
+ }),
+ );
+
+ vi.clearAllMocks();
+ });
+});
diff --git a/apps/wallet/src/components/accounts/AddAccountAssetDialog.vue b/apps/wallet/src/components/accounts/AddAccountAssetDialog.vue
new file mode 100644
index 000000000..7959fc9d8
--- /dev/null
+++ b/apps/wallet/src/components/accounts/AddAccountAssetDialog.vue
@@ -0,0 +1,137 @@
+
+
+
+
+ {{ $t('pages.account.add_asset') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('terms.submit') }}
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/accounts/BatchTransfersActionBtn.vue b/apps/wallet/src/components/accounts/BatchTransfersActionBtn.vue
index 0ff2cdbe2..35ea1f8a4 100644
--- a/apps/wallet/src/components/accounts/BatchTransfersActionBtn.vue
+++ b/apps/wallet/src/components/accounts/BatchTransfersActionBtn.vue
@@ -83,7 +83,7 @@
- {{ formatBalance(transfer.amount, account.decimals) }}
+ {{ formatBalance(transfer.amount, asset.decimals) }}
@@ -96,7 +96,7 @@
{{ $t('terms.total') }}:
- {{ formatBalance(totalAmount, account.decimals) }}
+ {{ formatBalance(totalAmount, asset.decimals) }}
|
@@ -157,8 +157,7 @@ import {
VToolbarTitle,
} from 'vuetify/components';
import logger from '~/core/logger.core';
-import { Account, Transfer, TransferOperationInput } from '~/generated/station/station.did';
-import { ChainApiFactory } from '~/services/chains';
+import { Account, Asset, TransferOperationInput } from '~/generated/station/station.did';
import { useAppStore } from '~/stores/app.store';
import { useStationStore } from '~/stores/station.store';
import { CsvTable } from '~/types/app.types';
@@ -167,6 +166,7 @@ import {
registerBeforeUnloadConfirmation,
unregisterBeforeUnloadConfirmation,
} from '~/utils/app.utils';
+import { detectAddressStandard } from '~/utils/asset.utils';
import { downloadCsv, readFileAsCsvTable } from '~/utils/file.utils';
import { requiredRule } from '~/utils/form.utils';
import {
@@ -179,6 +179,7 @@ import {
const props = withDefaults(
defineProps<{
account: Account;
+ asset: Asset;
batchChunkSize?: number;
icon?: string;
text?: string;
@@ -228,9 +229,8 @@ const hasInvalidTransfers = computed(() => rows.value.some(row => !row.valid));
const rawCsvTable = ref(null);
const invalidRawCsvTable = ref(null);
const downloadingInvalid = ref(false);
-const chainApi = computed(() => ChainApiFactory.create(props.account));
-type CsvTransferWithComment = Partial & { comment?: string };
+type CsvTransferWithComment = Partial & { comment?: string };
const rows = ref<
{
@@ -296,20 +296,29 @@ watch(
for (const row of rawCsvTable.value.rows) {
const transfer: CsvTransferWithComment = {};
let valid = true;
+ const maybeToAddress = row?.[csvToColumn.value];
+ if (maybeToAddress !== undefined) {
+ const maybeStandard = detectAddressStandard(
+ props.asset,
+ maybeToAddress,
+ station.configuration.details.supported_blockchains,
+ );
- if (
- row?.[csvToColumn.value] !== undefined &&
- chainApi.value.isValidAddress(row[csvToColumn.value])
- ) {
- transfer.to = row[csvToColumn.value];
+ if (maybeStandard) {
+ transfer.to = maybeToAddress;
+ transfer.with_standard = maybeStandard.standard;
+ } else {
+ valid = false;
+ }
}
+
if (row?.[csvCommentColumn.value] !== undefined) {
transfer.comment = row[csvCommentColumn.value];
}
if (row?.[csvAmountColumn.value] !== undefined) {
try {
- transfer.amount = amountToBigInt(row[csvAmountColumn.value], props.account.decimals);
+ transfer.amount = amountToBigInt(row[csvAmountColumn.value], props.asset.decimals);
} catch (e) {
valid = false;
}
@@ -378,10 +387,12 @@ const startBatchTransfer = async (): Promise => {
rowId,
transfer: {
from_account_id: props.account.id,
+ from_asset_id: props.asset.id,
+ with_standard: assertAndReturn(row.transfer.with_standard, 'with_standard'),
amount: assertAndReturn(row.transfer.amount, 'amount'),
to: maybeTransformBlockchainAddress(
- props.account.blockchain,
- props.account.standard,
+ props.asset.blockchain,
+ assertAndReturn(row.transfer.with_standard, 'with_standard'),
assertAndReturn(row.transfer.to, 'to'),
),
network: [],
diff --git a/apps/wallet/src/components/accounts/RemoveAssetDialog.spec.ts b/apps/wallet/src/components/accounts/RemoveAssetDialog.spec.ts
new file mode 100644
index 000000000..049f053fd
--- /dev/null
+++ b/apps/wallet/src/components/accounts/RemoveAssetDialog.spec.ts
@@ -0,0 +1,99 @@
+import { afterAll, describe, expect, it, vi } from 'vitest';
+import { Account } from '~/generated/station/station.did';
+import { services } from '~/plugins/services.plugin';
+import { StationService } from '~/services/station.service';
+import { mount } from '~/test.utils';
+import RemoveAssetDialog from './RemoveAssetDialog.vue';
+
+vi.mock('~/services/station.service', () => {
+ const mock: Partial = {
+ editAccount: vi.fn().mockImplementation(() => Promise.resolve({} as Account)),
+ };
+
+ return {
+ StationService: vi.fn(() => mock),
+ };
+});
+
+const mockAccount: Account = {
+ id: '1',
+ assets: [
+ {
+ asset_id: '1',
+ balance: [],
+ },
+ ],
+ addresses: [],
+ configs_request_policy: [],
+ metadata: [],
+ last_modification_timestamp: '2021-09-01T00:00:00Z',
+ name: 'Test',
+ transfer_request_policy: [],
+};
+
+describe('RemoveAssetDialog', () => {
+ afterAll(() => {});
+
+ it('renders correctly', () => {
+ const wrapper = mount(RemoveAssetDialog, {
+ props: {
+ account: mockAccount,
+ asset: '1',
+
+ open: true,
+ attach: true,
+ },
+ });
+
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('removes the asset when confirmed', async () => {
+ const wrapper = mount(RemoveAssetDialog, {
+ props: {
+ account: mockAccount,
+ asset: '1',
+
+ open: true,
+ attach: true,
+ },
+ });
+
+ const saveButton = wrapper.find('button[data-test-id="remove-asset-dialog-confirm-button');
+
+ await saveButton.trigger('click');
+
+ expect(services().station.editAccount).toHaveBeenCalledWith(
+ expect.objectContaining({
+ change_assets: [
+ {
+ Change: {
+ add_assets: [],
+ remove_assets: ['1'],
+ },
+ },
+ ],
+ }),
+ );
+
+ vi.clearAllMocks();
+ });
+
+ it('does not remove the asset when canceled', async () => {
+ const wrapper = mount(RemoveAssetDialog, {
+ props: {
+ account: mockAccount,
+ asset: '1',
+
+ open: true,
+ attach: true,
+ },
+ });
+
+ const cancelButton = wrapper.find('button[data-test-id="remove-asset-dialog-cancel-button');
+
+ await cancelButton.trigger('click');
+
+ expect(services().station.editAccount).not.toHaveBeenCalled();
+ });
+});
diff --git a/apps/wallet/src/components/accounts/RemoveAssetDialog.vue b/apps/wallet/src/components/accounts/RemoveAssetDialog.vue
new file mode 100644
index 000000000..8f16d246c
--- /dev/null
+++ b/apps/wallet/src/components/accounts/RemoveAssetDialog.vue
@@ -0,0 +1,122 @@
+
+
+
+
+ {{ $t('pages.account.remove_asset') }}
+
+
+
+ {{ $t('pages.account.remove_asset_confirm') }}
+
+
+
+
+
+ {{ $t('terms.cancel') }}
+
+
+ {{ $t('terms.remove') }}
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/accounts/TransferBtn.vue b/apps/wallet/src/components/accounts/TransferBtn.vue
index cbc53a726..c4a249fed 100644
--- a/apps/wallet/src/components/accounts/TransferBtn.vue
+++ b/apps/wallet/src/components/accounts/TransferBtn.vue
@@ -16,6 +16,7 @@
({
+ detectAddressStandard: vi.fn(() => 'icp_native'),
+ detectAddressFormat: vi.fn(() => AddressFormat.ICPNative),
+}));
vi.mock('~/services/station.service', () => ({
StationService: vi.fn().mockImplementation(() => {
@@ -16,14 +30,24 @@ vi.mock('~/services/station.service', () => ({
}),
}));
+const mockAsset: Asset = {
+ blockchain: 'icp',
+ decimals: 2,
+ id: '1',
+ metadata: [],
+ name: 'ICP',
+ symbol: 'ICP',
+ standards: ['icp_native', 'icrc1'],
+};
+
describe('TransferDialog', () => {
it('renders correctly', () => {
const wrapper = mount(TransferDialog, {
props: {
account: {
id: '1',
- decimals: 1,
} as Account,
+ asset: mockAsset,
open: true,
},
});
@@ -35,8 +59,8 @@ describe('TransferDialog', () => {
props: {
account: {
id: '1',
- decimals: 1,
} as Account,
+ asset: mockAsset,
open: true,
},
});
@@ -74,8 +98,8 @@ describe('TransferDialog', () => {
props: {
account: {
id: '1',
- decimals: 1,
} as Account,
+ asset: mockAsset,
open: true,
},
});
@@ -92,21 +116,20 @@ describe('TransferDialog', () => {
const submitButton = form.find(`[data-test-id="transfer-dialog-save-button"]`);
- amount.find('input').setValue('1');
- destination.find('input').setValue('destination address');
- summary.find('input').setValue('test summary');
+ await amount.find('input').setValue('1');
+ await destination.find('input').setValue(validIcpAddress);
+ await summary.find('input').setValue('test summary');
await flushPromises();
await submitButton.trigger('click');
- await wrapper.vm.$nextTick();
await flushPromises();
expect(services().station.transfer).toHaveBeenCalledWith(
expect.objectContaining({
- amount: 10n,
- to: 'destination address',
+ amount: 100n, // decimals are 2
+ to: validIcpAddress,
}),
'test summary',
);
@@ -123,7 +146,7 @@ describe('TransferDialog', () => {
services().station.getTransfer = vi.fn(() =>
Promise.resolve({
id: 'transfer-id',
- to: 'destination address',
+ to: validIcpAddress,
amount: 123n,
request_id: 'request-id',
} as Transfer),
@@ -133,8 +156,8 @@ describe('TransferDialog', () => {
props: {
account: {
id: '1',
- decimals: 2,
} as Account,
+ asset: mockAsset,
open: true,
transferId: 'transfer-id',
},
@@ -161,7 +184,7 @@ describe('TransferDialog', () => {
expect(transferId.find('input').element.value).toBe('transfer-id');
expect(amount.find('input').element.value).toBe('1.23');
- expect(destination.find('input').element.value).toBe('destination address');
+ expect(destination.find('input').element.value).toBe(validIcpAddress);
expect(summary.find('input').element.value).toBe('test summary');
});
});
diff --git a/apps/wallet/src/components/accounts/TransferDialog.vue b/apps/wallet/src/components/accounts/TransferDialog.vue
index 3a851f086..972fe9940 100644
--- a/apps/wallet/src/components/accounts/TransferDialog.vue
+++ b/apps/wallet/src/components/accounts/TransferDialog.vue
@@ -17,7 +17,9 @@
>
- {{ $t('terms.transfer') }}
+ {{
+ $t('terms.transfer_asset', { asset: props.asset.value.symbol })
+ }}
@@ -26,6 +28,7 @@
v-model="transfer"
v-model:trigger-submit="triggerSubmit"
:account="props.account.value"
+ :asset="props.asset.value"
:mode="props.readonly.value ? 'view' : 'edit'"
@submit="save"
@valid="valid = $event"
@@ -89,15 +92,18 @@ import {
useOnSuccessfulOperation,
} from '~/composables/notifications.composable';
import logger from '~/core/logger.core';
-import { Account, Request, Transfer, UUID } from '~/generated/station/station.did';
+import { Account, Asset, Request, Transfer, UUID } from '~/generated/station/station.did';
import { services } from '~/plugins/services.plugin';
import { maybeTransformBlockchainAddress } from '~/utils/app.utils';
import { assertAndReturn } from '~/utils/helper.utils';
import TransferForm from './TransferForm.vue';
+import { detectAddressStandard } from '~/utils/asset.utils';
+import { useStationStore } from '~/stores/station.store';
const input = withDefaults(
defineProps<{
account: Account;
+ asset: Asset;
transferId?: UUID;
open?: boolean;
dialogMaxWidth?: number;
@@ -135,6 +141,8 @@ const summary = computed({
const stationService = services().station;
+const stationStore = useStationStore();
+
const loadTransfer = async (): Promise<{
transfer: Partial;
request: Partial;
@@ -171,14 +179,28 @@ const save = async (): Promise => {
try {
saving.value = true;
+ const toAddress = assertAndReturn(transfer.value.to, 'to');
+
+ const maybeStandard = detectAddressStandard(
+ props.asset.value,
+ toAddress,
+ stationStore.configuration.details.supported_blockchains,
+ );
+
+ if (!maybeStandard) {
+ throw new Error('Invalid address');
+ }
+
const newRequest = await stationService.transfer(
{
from_account_id: assertAndReturn(transfer.value.from_account_id, 'from_account_id'),
+ from_asset_id: props.asset.value.id,
+ with_standard: maybeStandard.standard,
amount: assertAndReturn(transfer.value.amount, 'amount'),
to: maybeTransformBlockchainAddress(
- props.account.value.blockchain,
- props.account.value.standard,
- assertAndReturn(transfer.value.to, 'to'),
+ props.asset.value.blockchain,
+ maybeStandard.standard,
+ toAddress,
),
fee: transfer.value.fee ? [transfer.value.fee] : [],
metadata: transfer.value.metadata ?? [],
diff --git a/apps/wallet/src/components/accounts/TransferForm.vue b/apps/wallet/src/components/accounts/TransferForm.vue
index 27999fc9f..24832531c 100644
--- a/apps/wallet/src/components/accounts/TransferForm.vue
+++ b/apps/wallet/src/components/accounts/TransferForm.vue
@@ -20,7 +20,7 @@
:readonly="isViewMode"
type="text"
:prepend-icon="mdiSend"
- :rules="[requiredRule]"
+ :rules="[requiredRule, addressValidator]"
data-test-id="transfer-form-destination-address"
/>
@@ -46,13 +46,14 @@ import { onUnmounted } from 'vue';
import { onMounted } from 'vue';
import { computed, ref, toRefs, watch } from 'vue';
import { VForm, VTextField } from 'vuetify/components';
-import { Account, Transfer } from '~/generated/station/station.did';
+import { Account, Asset, Transfer } from '~/generated/station/station.did';
import { VFormValidation } from '~/types/helper.types';
-import { requiredRule, validTokenAmount } from '~/utils/form.utils';
+import { requiredRule, validAddress, validTokenAmount } from '~/utils/form.utils';
import { amountToBigInt, formatBalance } from '~/utils/helper.utils';
export type TransferFormProps = {
account: Account;
+ asset: Asset;
modelValue: Partial;
triggerSubmit?: boolean;
valid?: boolean;
@@ -86,13 +87,15 @@ const emit = defineEmits<{
const model = computed(() => props.modelValue.value);
watch(model.value, newValue => emit('update:modelValue', newValue), { deep: true });
+const addressValidator = computed(() => validAddress(input.asset.blockchain));
+
const amountInput = ref(null);
const amount = ref(undefined);
watch(
() => model.value.amount,
newValue => {
amount.value =
- newValue !== undefined ? formatBalance(newValue, props.account.value.decimals) : undefined;
+ newValue !== undefined ? formatBalance(newValue, props.asset.value.decimals) : undefined;
},
{ immediate: true },
);
@@ -100,9 +103,9 @@ watch(
const syncAmountInput = (): void => {
if (
amount.value !== undefined &&
- validTokenAmount(amount.value, props.account.value.decimals) === true
+ validTokenAmount(amount.value, props.asset.value.decimals) === true
) {
- model.value.amount = amountToBigInt(amount.value, props.account.value.decimals);
+ model.value.amount = amountToBigInt(amount.value, props.asset.value.decimals);
} else {
model.value.amount = undefined;
}
diff --git a/apps/wallet/src/components/accounts/wizard/AccountConfigurationSettings.vue b/apps/wallet/src/components/accounts/wizard/AccountConfigurationSettings.vue
index 77c1e95e1..266a1bc35 100644
--- a/apps/wallet/src/components/accounts/wizard/AccountConfigurationSettings.vue
+++ b/apps/wallet/src/components/accounts/wizard/AccountConfigurationSettings.vue
@@ -15,7 +15,7 @@
@@ -31,7 +31,7 @@
v-model="model.name"
name="name"
:label="$t('terms.name')"
- :rules="[requiredRule]"
+ :rules="[requiredRule, maxLengthRule(64, $t('terms.name'))]"
variant="filled"
class="mb-2"
density="comfortable"
@@ -47,15 +47,13 @@ import { mdiBank, mdiIdentifier, mdiWallet } from '@mdi/js';
import { computed } from 'vue';
import { VCol, VRow, VTextField } from 'vuetify/components';
import TokenAutocomplete from '~/components/inputs/TokenAutocomplete.vue';
-import { TimestampRFC3339, UUID, Asset } from '~/generated/station/station.did';
-import { requiredRule } from '~/utils/form.utils';
+import { TimestampRFC3339, UUID } from '~/generated/station/station.did';
+import { maxLengthRule, requiredRule } from '~/utils/form.utils';
export interface AccountConfigurationModel {
id: UUID;
name: string;
- blockchain: string;
- standard: string;
- symbol: string;
+ assets: UUID[];
lastModified: TimestampRFC3339;
}
@@ -83,20 +81,14 @@ const emit = defineEmits<{
}>();
const isViewMode = computed(() => props.mode === 'view');
+
const model = computed({
get: () => props.modelValue,
set: value => emit('update:modelValue', value),
});
-const onSelectedAsset = (asset?: Asset): void => {
- if (asset) {
- model.value.symbol = asset.symbol;
- model.value.blockchain = asset.blockchain;
- model.value.standard = asset.standard;
- } else {
- model.value.symbol = undefined;
- model.value.blockchain = undefined;
- model.value.standard = undefined;
- }
-};
+const assetIds = computed({
+ get: () => model.value.assets,
+ set: value => (model.value.assets = value),
+});
diff --git a/apps/wallet/src/components/address-book/AddressBookDialog.vue b/apps/wallet/src/components/address-book/AddressBookDialog.vue
index 7cb31a5ab..cc85d6034 100644
--- a/apps/wallet/src/components/address-book/AddressBookDialog.vue
+++ b/apps/wallet/src/components/address-book/AddressBookDialog.vue
@@ -76,6 +76,7 @@ import logger from '~/core/logger.core';
import { AddressBookEntry, UUID } from '~/generated/station/station.did';
import { useStationStore } from '~/stores/station.store';
import { BlockchainStandard } from '~/types/chain.types';
+import { detectAddressFormat } from '~/utils/asset.utils';
import { assertAndReturn } from '~/utils/helper.utils';
const input = withDefaults(
@@ -160,11 +161,20 @@ const save = async (): Promise => {
return;
}
+ const blockchain = assertAndReturn(addressBookEntry.value.blockchain, 'blockchain');
+ const address = assertAndReturn(addressBookEntry.value.address, 'address');
+ const maybeAddressFormat = detectAddressFormat(blockchain, address);
+
+ if (!maybeAddressFormat) {
+ throw new Error(`Invalid address for blockchain ${blockchain}`);
+ }
+
const request = await station.service.addAddressBookEntry({
- blockchain: assertAndReturn(addressBookEntry.value.blockchain, 'blockchain'),
+ blockchain,
labels: assertAndReturn(addressBookEntry.value.labels, 'labels'),
address_owner: assertAndReturn(addressBookEntry.value.address_owner, 'address_owner'),
- address: assertAndReturn(addressBookEntry.value.address, 'address'),
+ address,
+ address_format: maybeAddressFormat,
metadata: addressBookEntry.value.metadata ?? [],
});
diff --git a/apps/wallet/src/components/address-book/AddressBookForm.vue b/apps/wallet/src/components/address-book/AddressBookForm.vue
index f02b7ca34..8f31b3d15 100644
--- a/apps/wallet/src/components/address-book/AddressBookForm.vue
+++ b/apps/wallet/src/components/address-book/AddressBookForm.vue
@@ -52,7 +52,6 @@ import BlockchainAutocomplete from '~/components/inputs/BlockchainAutocomplete.v
import MetadataField from '~/components/inputs/MetadataField.vue';
import { AddressBookEntry, Asset } from '~/generated/station/station.did';
import { useStationStore } from '~/stores/station.store';
-import { BlockchainStandard } from '~/types/chain.types';
import { VFormValidation } from '~/types/helper.types';
import { requiredRule } from '~/utils/form.utils';
@@ -95,7 +94,7 @@ const station = useStationStore();
const onSelectedBlockchain = (asset?: Asset): void => {
if (asset) {
model.value.blockchain = asset.blockchain;
- model.value.labels = [BlockchainStandard.Native];
+ model.value.labels = [];
} else {
model.value.blockchain = undefined;
model.value.labels = undefined;
diff --git a/apps/wallet/src/components/assets/AssetDialog.spec.ts b/apps/wallet/src/components/assets/AssetDialog.spec.ts
new file mode 100644
index 000000000..dd7a0f196
--- /dev/null
+++ b/apps/wallet/src/components/assets/AssetDialog.spec.ts
@@ -0,0 +1,138 @@
+import { describe, expect, it, vi } from 'vitest';
+import AssetDialog from './AssetDialog.vue';
+import { mount } from '~/test.utils';
+import { StationService } from '~/services/station.service';
+import { Capabilities, GetAssetResult } from '~/generated/station/station.did';
+import { ExtractOk } from '~/types/helper.types';
+import { services } from '~/plugins/services.plugin';
+import AssetForm from './AssetForm.vue';
+import { flushPromises } from '@vue/test-utils';
+import { VCard } from 'vuetify/components';
+import { BlockchainStandard } from '~/types/chain.types';
+
+vi.mock('~/services/station.service', () => {
+ const mock: Partial = {
+ withStationId: vi.fn().mockReturnThis(),
+ capabilities: vi.fn().mockImplementation(() =>
+ Promise.resolve({
+ supported_blockchains: [
+ {
+ blockchain: 'icp',
+ supported_standards: [{ standard: BlockchainStandard.Native }],
+ },
+ ],
+ } as Capabilities),
+ ),
+ addAsset: vi.fn().mockImplementation(() => Promise.resolve({} as Request)),
+ getAsset: vi.fn().mockImplementation(() =>
+ Promise.resolve({
+ asset: {
+ id: '1',
+ blockchain: 'icp',
+ decimals: 8,
+ metadata: [
+ {
+ key: 'ledger_canister_id',
+ value: 'ryjl3-tyaaa-aaaaa-aaaba-cai',
+ },
+ {
+ key: 'index_canister_id',
+ value: 'qhbym-qaaaa-aaaaa-aaafq-cai',
+ },
+ ],
+ standards: [BlockchainStandard.Native],
+ name: 'Test',
+ symbol: 'TEST',
+ },
+ privileges: {},
+ } as ExtractOk),
+ ),
+ };
+
+ return {
+ StationService: vi.fn(() => mock),
+ };
+});
+
+describe('AssetDialog', () => {
+ it('renders correctly', () => {
+ const wrapper = mount(AssetDialog, {
+ props: {
+ open: true,
+ },
+ });
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('loads and displays existing asset', async () => {
+ const wrapper = mount(AssetDialog, {
+ props: {
+ open: true,
+ assetId: '1',
+ },
+ });
+
+ await flushPromises();
+
+ // expect getAsset to be called
+ expect(services().station.getAsset).toHaveBeenCalled();
+
+ const form = wrapper.findComponent(AssetForm);
+
+ const name = form.find('input[name="name"]').element as HTMLInputElement;
+ const symbol = form.find('input[name="symbol"]').element as HTMLInputElement;
+ const decimals = form.find('input[name="decimals"]').element as HTMLInputElement;
+ const ledger = form.find('input[name="metadata_ledger_canister_id"]')
+ .element as HTMLInputElement;
+ const index = form.find('input[name="metadata_index_canister_id"]').element as HTMLInputElement;
+
+ expect(name.value).toBe('Test');
+ expect(symbol.value).toBe('TEST');
+ expect(decimals.value).toBe('8');
+ expect(ledger.value).toBe('ryjl3-tyaaa-aaaaa-aaaba-cai');
+ expect(index.value).toBe('qhbym-qaaaa-aaaaa-aaafq-cai');
+ });
+
+ it('creates new asset', async () => {
+ const wrapper = mount(AssetDialog, {
+ props: {
+ open: true,
+ },
+ });
+
+ await flushPromises();
+
+ const dialogContents = wrapper.findComponent(VCard);
+
+ const form = wrapper.findComponent(AssetForm);
+
+ await form
+ .findComponent({ name: 'BlockchainAutocomplete' })
+ .vm.$emit('update:modelValue', 'icp');
+
+ await form
+ .findComponent({ name: 'StandardsAutocomplete' })
+ .vm.$emit('update:modelValue', [BlockchainStandard.Native]);
+
+ // fill out form
+ await form.find('input[name="name"]').setValue('Test');
+ await form.find('input[name="symbol"]').setValue('TEST');
+ await form.find('input[name="decimals"]').setValue('8');
+ await form
+ .find('input[name="metadata_ledger_canister_id"]')
+ .setValue('ryjl3-tyaaa-aaaaa-aaaba-cai');
+ await form
+ .find('input[name="metadata_index_canister_id"]')
+ .setValue('qhbym-qaaaa-aaaaa-aaafq-cai');
+
+ await flushPromises();
+
+ const saveButton = dialogContents.find('[data-test-id="save-asset"]');
+
+ await saveButton.trigger('click');
+
+ await flushPromises();
+
+ expect(services().station.addAsset).toHaveBeenCalled();
+ });
+});
diff --git a/apps/wallet/src/components/assets/AssetDialog.vue b/apps/wallet/src/components/assets/AssetDialog.vue
new file mode 100644
index 000000000..86e2757c8
--- /dev/null
+++ b/apps/wallet/src/components/assets/AssetDialog.vue
@@ -0,0 +1,178 @@
+
+
+
+
+
+ {{ $t('app.asset') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.assetId.value ? $t('terms.save') : $t('terms.create') }}
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/assets/AssetDialogBtn.vue b/apps/wallet/src/components/assets/AssetDialogBtn.vue
new file mode 100644
index 000000000..b03c82b87
--- /dev/null
+++ b/apps/wallet/src/components/assets/AssetDialogBtn.vue
@@ -0,0 +1,66 @@
+
+
+
+
+ {{ props.text.value }}
+
+
+
+
+ {
+ open = openEvent;
+
+ emit('opened', openEvent);
+ }
+ "
+ />
+
+
diff --git a/apps/wallet/src/components/assets/AssetForm.spec.ts b/apps/wallet/src/components/assets/AssetForm.spec.ts
new file mode 100644
index 000000000..7a1c9af67
--- /dev/null
+++ b/apps/wallet/src/components/assets/AssetForm.spec.ts
@@ -0,0 +1,56 @@
+import { describe, expect, it } from 'vitest';
+import AssetForm from './AssetForm.vue';
+import { mount } from '~/test.utils';
+import { BlockchainStandard } from '~/types/chain.types';
+
+describe('AssetForm', () => {
+ it('renders correctly for creation', () => {
+ const wrapper = mount(AssetForm, {
+ props: {
+ modelValue: {},
+ },
+ });
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('shows custom metadata form for ICP', async () => {
+ const wrapper = mount(AssetForm, {
+ props: {
+ modelValue: {
+ blockchain: 'icp',
+ standards: [BlockchainStandard.Native],
+ metadata: [],
+ },
+ },
+ });
+
+ expect(wrapper.find('input[name="metadata_ledger_canister_id"]').exists()).toBe(true);
+ expect(wrapper.find('input[name="metadata_index_canister_id"]').exists()).toBe(true);
+ });
+
+ it('emits valid form', async () => {
+ const wrapper = mount(AssetForm, {
+ props: {
+ modelValue: {
+ blockchain: 'icp',
+ standards: [BlockchainStandard.Native],
+ metadata: [],
+ decimals: 8,
+ name: 'Test',
+ symbol: 'TEST',
+ },
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted('valid')).toEqual([[false]]);
+
+ // fill out metadata for ICP
+ await wrapper
+ .find('input[name="metadata_ledger_canister_id"]')
+ .setValue('ryjl3-tyaaa-aaaaa-aaaba-cai');
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted('valid')).toEqual([[false], [true]]);
+ });
+});
diff --git a/apps/wallet/src/components/assets/AssetForm.vue b/apps/wallet/src/components/assets/AssetForm.vue
new file mode 100644
index 000000000..32f1fde40
--- /dev/null
+++ b/apps/wallet/src/components/assets/AssetForm.vue
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/assets/standards/InternetComputerNativeStandardForm.spec.ts b/apps/wallet/src/components/assets/standards/InternetComputerNativeStandardForm.spec.ts
new file mode 100644
index 000000000..0a416932f
--- /dev/null
+++ b/apps/wallet/src/components/assets/standards/InternetComputerNativeStandardForm.spec.ts
@@ -0,0 +1,43 @@
+import { describe, expect, it } from 'vitest';
+import { mount } from '~/test.utils';
+import InternetComputerNativeStandardForm from './InternetComputerNativeStandardForm.vue';
+
+describe('InternetComputerNativeStandardForm', () => {
+ it('renders correctly for creation', () => {
+ const wrapper = mount(InternetComputerNativeStandardForm, {
+ props: {
+ modelValue: [],
+ readonly: false,
+ },
+ });
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('initializes custom form from metadata', async () => {
+ const wrapper = mount(InternetComputerNativeStandardForm, {
+ props: {
+ modelValue: [
+ {
+ key: 'ledger_canister_id',
+ value: 'ryjl3-tyaaa-aaaaa-aaaba-cai',
+ },
+ {
+ key: 'index_canister_id',
+ value: 'qhbym-qaaaa-aaaaa-aaafq-cai',
+ },
+ ],
+ readonly: false,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ const ledgerInput = wrapper.find('input[name="metadata_ledger_canister_id"]')
+ .element as HTMLInputElement;
+ const indexInput = wrapper.find('input[name="metadata_index_canister_id"]')
+ .element as HTMLInputElement;
+
+ expect(ledgerInput.value).toBe('ryjl3-tyaaa-aaaaa-aaaba-cai');
+ expect(indexInput.value).toBe('qhbym-qaaaa-aaaaa-aaafq-cai');
+ });
+});
diff --git a/apps/wallet/src/components/assets/standards/InternetComputerNativeStandardForm.vue b/apps/wallet/src/components/assets/standards/InternetComputerNativeStandardForm.vue
new file mode 100644
index 000000000..712dcb235
--- /dev/null
+++ b/apps/wallet/src/components/assets/standards/InternetComputerNativeStandardForm.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
diff --git a/apps/wallet/src/components/inputs/AssetAutocomplete.spec.ts b/apps/wallet/src/components/inputs/AssetAutocomplete.spec.ts
new file mode 100644
index 000000000..8cf525b23
--- /dev/null
+++ b/apps/wallet/src/components/inputs/AssetAutocomplete.spec.ts
@@ -0,0 +1,49 @@
+import { describe, expect, it, vi } from 'vitest';
+import { StationService } from '~/services/station.service';
+import { mount } from '~/test.utils';
+import AssetAutocomplete from './AssetAutocomplete.vue';
+
+vi.mock('~/services/station.service', () => {
+ const mock: Partial = {
+ withStationId: vi.fn().mockReturnThis(),
+ listAssets: vi.fn().mockImplementation(() =>
+ Promise.resolve({
+ assets: [],
+ next_offset: [BigInt(0)],
+ total: BigInt(0),
+ }),
+ ),
+ };
+
+ return {
+ StationService: vi.fn(() => mock),
+ };
+});
+
+describe('AssetAutocomplete', () => {
+ it('renders with selected ids', () => {
+ const wrapper = mount(AssetAutocomplete, {
+ props: {
+ modelValue: ['1'],
+ },
+ });
+
+ expect(wrapper.exists()).toBe(true);
+
+ const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' });
+ expect(autocomplete.exists()).toBe(true);
+
+ expect(autocomplete.props('modelValue')).toEqual(['1']);
+ });
+
+ it('renders with empty list of assets', async () => {
+ const wrapper = mount(AssetAutocomplete);
+ const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' });
+
+ expect(autocomplete.exists()).toBe(true);
+
+ await wrapper.vm.$nextTick();
+
+ expect(autocomplete.props('items')).toEqual([]);
+ });
+});
diff --git a/apps/wallet/src/components/inputs/AssetAutocomplete.vue b/apps/wallet/src/components/inputs/AssetAutocomplete.vue
new file mode 100644
index 000000000..f95e7ff4d
--- /dev/null
+++ b/apps/wallet/src/components/inputs/AssetAutocomplete.vue
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/apps/wallet/src/components/inputs/MetadataField.vue b/apps/wallet/src/components/inputs/MetadataField.vue
index df8c5a5da..a9922190d 100644
--- a/apps/wallet/src/components/inputs/MetadataField.vue
+++ b/apps/wallet/src/components/inputs/MetadataField.vue
@@ -13,38 +13,40 @@
-
-
- |
-
-
- |
-
-
-
+
+
-
- |
+ |
+
+
+ |
+
+
+
+
+ |
+
@@ -80,6 +82,7 @@ const input = withDefaults(
density?: 'comfortable' | 'compact';
readonly?: boolean;
disabled?: boolean;
+ hideKeys?: string[];
}>(),
{
modelValue: () => [],
@@ -87,6 +90,7 @@ const input = withDefaults(
density: 'comfortable',
readonly: false,
disabled: false,
+ hideKeys: () => [],
},
);
diff --git a/apps/wallet/src/components/inputs/StandardsAutocomplete.spec.ts b/apps/wallet/src/components/inputs/StandardsAutocomplete.spec.ts
new file mode 100644
index 000000000..e4e8334f2
--- /dev/null
+++ b/apps/wallet/src/components/inputs/StandardsAutocomplete.spec.ts
@@ -0,0 +1,36 @@
+import { describe, expect, it } from 'vitest';
+import { mount } from '~/test.utils';
+import StandardsAutocomplete from './StandardsAutocomplete.vue';
+
+describe('StandardsAutocomplete', () => {
+ it('renders with selected ids', () => {
+ const wrapper = mount(StandardsAutocomplete, {
+ props: {
+ modelValue: ['1'],
+ blockchain: 'icp',
+ },
+ });
+
+ expect(wrapper.exists()).toBe(true);
+
+ const autocomplete = wrapper.findComponent({ name: 'VSelect' });
+ expect(autocomplete.exists()).toBe(true);
+
+ expect(autocomplete.props('modelValue')).toEqual(['1']);
+ });
+
+ it('renders with empty list of standards', async () => {
+ const wrapper = mount(StandardsAutocomplete, {
+ props: {
+ blockchain: 'icp',
+ },
+ });
+ const autocomplete = wrapper.findComponent({ name: 'VSelect' });
+
+ expect(autocomplete.exists()).toBe(true);
+
+ await wrapper.vm.$nextTick();
+
+ expect(autocomplete.props('items')).toEqual([]);
+ });
+});
diff --git a/apps/wallet/src/components/inputs/StandardsAutocomplete.vue b/apps/wallet/src/components/inputs/StandardsAutocomplete.vue
new file mode 100644
index 000000000..189d1423a
--- /dev/null
+++ b/apps/wallet/src/components/inputs/StandardsAutocomplete.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
diff --git a/apps/wallet/src/components/inputs/TokenAutocomplete.vue b/apps/wallet/src/components/inputs/TokenAutocomplete.vue
index 9f3d8c35b..d47fb97c5 100644
--- a/apps/wallet/src/components/inputs/TokenAutocomplete.vue
+++ b/apps/wallet/src/components/inputs/TokenAutocomplete.vue
@@ -3,26 +3,29 @@
v-model="model"
:multiple="props.multiple.value"
:label="props.label.value"
- item-value="value"
- item-title="text"
+ item-value="id"
+ :item-title="item => `${item.name} (${item.symbol})`"
:items="items"
:variant="props.variant.value"
:density="props.density.value"
:readonly="props.readonly.value"
:disabled="props.disabled.value"
:rules="props.rules.value"
+ :no-data-text="props.noDataText.value"
+ data-test-id="token-autocomplete"
/>
diff --git a/apps/wallet/src/components/request-policies/specifier/AssetSpecifier.vue b/apps/wallet/src/components/request-policies/specifier/AssetSpecifier.vue
new file mode 100644
index 000000000..9961411eb
--- /dev/null
+++ b/apps/wallet/src/components/request-policies/specifier/AssetSpecifier.vue
@@ -0,0 +1,91 @@
+
+
+
+
+ {{ $t('terms.all') }}
+
+
+ {{ $t('terms.subset') }}
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/request-policies/specifier/SpecifierSelector.vue b/apps/wallet/src/components/request-policies/specifier/SpecifierSelector.vue
index 170716ef7..ce9fb54f6 100644
--- a/apps/wallet/src/components/request-policies/specifier/SpecifierSelector.vue
+++ b/apps/wallet/src/components/request-policies/specifier/SpecifierSelector.vue
@@ -37,6 +37,7 @@ import UserGroupSpecifier from './UserGroupSpecifier.vue';
import UserSpecifier from './UserSpecifier.vue';
import UnsupportedSpecifier from './UnsupportedSpecifier.vue';
import { VAutocomplete } from 'vuetify/components';
+import AssetSpecifier from './AssetSpecifier.vue';
const input = withDefaults(
defineProps<{
@@ -70,6 +71,8 @@ const componentsMap: {
} = {
AddUser: null,
AddUserGroup: null,
+ AddAsset: null,
+
AddAccount: null,
AddRequestPolicy: null,
AddAddressBookEntry: null,
@@ -83,6 +86,9 @@ const componentsMap: {
EditUser: UserSpecifier,
EditAddressBookEntry: AddressBookEntrySpecifier,
RemoveAddressBookEntry: AddressBookEntrySpecifier,
+ EditAsset: AssetSpecifier,
+ RemoveAsset: AssetSpecifier,
+
// below variants are not supported yet
EditPermission: UnsupportedSpecifier,
EditRequestPolicy: UnsupportedSpecifier,
@@ -247,6 +253,15 @@ watch(
case RequestSpecifierEnum.SetDisasterRecovery:
model.value = { [specifier.value]: null };
break;
+ case RequestSpecifierEnum.AddAsset:
+ model.value = { [specifier.value]: null };
+ break;
+ case RequestSpecifierEnum.EditAsset:
+ model.value = { [specifier.value]: { Any: null } };
+ break;
+ case RequestSpecifierEnum.RemoveAsset:
+ model.value = { [specifier.value]: { Any: null } };
+ break;
default:
unreachable(specifier.value);
}
diff --git a/apps/wallet/src/components/requests/RecentRequests.vue b/apps/wallet/src/components/requests/RecentRequests.vue
index 552d8c2f4..232ed0128 100644
--- a/apps/wallet/src/components/requests/RecentRequests.vue
+++ b/apps/wallet/src/components/requests/RecentRequests.vue
@@ -69,7 +69,7 @@ import RequestList from './RequestList.vue';
const props = withDefaults(
defineProps<{
- types: ListRequestsOperationType[];
+ types?: ListRequestsOperationType[];
title?: string;
limit?: number;
sortBy?: ListRequestsArgs['sortBy'];
@@ -82,6 +82,7 @@ const props = withDefaults(
}>(),
{
title: undefined,
+ types: undefined,
limit: 3,
sortBy: () => ({
expirationDt: 'asc',
diff --git a/apps/wallet/src/components/requests/RequestDetailView.vue b/apps/wallet/src/components/requests/RequestDetailView.vue
index 7430c72fa..600a0c1ac 100644
--- a/apps/wallet/src/components/requests/RequestDetailView.vue
+++ b/apps/wallet/src/components/requests/RequestDetailView.vue
@@ -241,6 +241,7 @@ import RequestMetadata from './RequestMetadata.vue';
import RequestStatusChip from './RequestStatusChip.vue';
import AddAccountOperation from './operations/AddAccountOperation.vue';
import AddAddressBookEntryOperation from './operations/AddAddressBookEntryOperation.vue';
+import AddAssetOperation from './operations/AddAssetOperation.vue';
import AddRequestPolicyOperation from './operations/AddRequestPolicyOperation.vue';
import AddUserGroupOperation from './operations/AddUserGroupOperation.vue';
import AddUserOperation from './operations/AddUserOperation.vue';
@@ -258,6 +259,8 @@ import RemoveUserGroupOperation from './operations/RemoveUserGroupOperation.vue'
import SystemUpgradeOperation from './operations/SystemUpgradeOperation.vue';
import TransferOperation from './operations/TransferOperation.vue';
import UnsupportedOperation from './operations/UnsupportedOperation.vue';
+import EditAssetOperation from './operations/EditAssetOperation.vue';
+import RemoveAssetOperation from './operations/RemoveAssetOperation.vue';
const i18n = useI18n();
@@ -294,6 +297,9 @@ const componentsMap: {
SystemUpgrade: SystemUpgradeOperation,
EditPermission: EditPermissionOperation,
ManageSystemInfo: ManageSystemInfoOperation,
+ AddAsset: AddAssetOperation,
+ EditAsset: EditAssetOperation,
+ RemoveAsset: RemoveAssetOperation,
CallExternalCanister: CallExternalCanisterOperation,
ChangeExternalCanister: UnsupportedOperation,
CreateExternalCanister: UnsupportedOperation,
diff --git a/apps/wallet/src/components/requests/RequestDialog.spec.ts b/apps/wallet/src/components/requests/RequestDialog.spec.ts
index fabd7f20c..2626b61e2 100644
--- a/apps/wallet/src/components/requests/RequestDialog.spec.ts
+++ b/apps/wallet/src/components/requests/RequestDialog.spec.ts
@@ -6,22 +6,34 @@ import {
GetRequestResultData,
RequestOperation,
RequestApproval,
+ Asset,
} from '~/generated/station/station.did';
import { services } from '~/plugins/services.plugin';
import { mount } from '~/test.utils';
import { ExtractOk } from '~/types/helper.types';
import RequestDialog from './RequestDialog.vue';
+const mockAsset: Asset = {
+ blockchain: 'icp',
+ decimals: 2,
+ id: '1',
+ metadata: [],
+ name: 'ICP',
+ symbol: 'ICP',
+ standards: ['icp_native', 'icrc1'],
+};
+
const transferOperation1 = {
Transfer: {
from_account: [
{
- address: 'fromaddress1',
+ addresses: [{ address: 'fromaddress1' }],
},
],
input: {
to: 'toaddress1',
},
+ from_asset: mockAsset,
},
} as RequestOperation;
@@ -29,12 +41,13 @@ const transferOperation2 = {
Transfer: {
from_account: [
{
- address: 'fromaddress2',
+ addresses: [{ address: 'fromaddress2' }],
},
],
input: {
to: 'toaddress2',
},
+ from_asset: mockAsset,
},
} as RequestOperation;
diff --git a/apps/wallet/src/components/requests/RequestListItem.vue b/apps/wallet/src/components/requests/RequestListItem.vue
index fc31661e0..b14947c7f 100644
--- a/apps/wallet/src/components/requests/RequestListItem.vue
+++ b/apps/wallet/src/components/requests/RequestListItem.vue
@@ -46,6 +46,7 @@ import { KeysOfUnion } from '~/utils/helper.utils';
import RequestStatusChip from './RequestStatusChip.vue';
import ReviewRequestBtn from './ReviewRequestBtn.vue';
import AddAccountOperation from './operations/AddAccountOperation.vue';
+import AddAssetOperation from './operations/AddAssetOperation.vue';
import AddAddressBookEntryOperation from './operations/AddAddressBookEntryOperation.vue';
import AddRequestPolicyOperation from './operations/AddRequestPolicyOperation.vue';
import AddUserGroupOperation from './operations/AddUserGroupOperation.vue';
@@ -64,6 +65,8 @@ import RemoveUserGroupOperation from './operations/RemoveUserGroupOperation.vue'
import SystemUpgradeOperation from './operations/SystemUpgradeOperation.vue';
import TransferOperation from './operations/TransferOperation.vue';
import UnsupportedOperation from './operations/UnsupportedOperation.vue';
+import EditAssetOperation from './operations/EditAssetOperation.vue';
+import RemoveAssetOperation from './operations/RemoveAssetOperation.vue';
const props = withDefaults(
defineProps<{
@@ -103,6 +106,11 @@ const componentsMap: {
EditPermission: EditPermissionOperation,
ManageSystemInfo: ManageSystemInfoOperation,
CallExternalCanister: CallExternalCanisterOperation,
+ AddAsset: AddAssetOperation,
+ EditAsset: EditAssetOperation,
+ RemoveAsset: RemoveAssetOperation,
+
+ // below variants are not supported yet
ChangeExternalCanister: UnsupportedOperation,
CreateExternalCanister: UnsupportedOperation,
ConfigureExternalCanister: UnsupportedOperation,
diff --git a/apps/wallet/src/components/requests/operations/AddAccountOperation.vue b/apps/wallet/src/components/requests/operations/AddAccountOperation.vue
index bf57e775f..0068af1f3 100644
--- a/apps/wallet/src/components/requests/operations/AddAccountOperation.vue
+++ b/apps/wallet/src/components/requests/operations/AddAccountOperation.vue
@@ -6,10 +6,10 @@
{{ accountSetup.configuration.name ?? '-' }}
-
- {{ $t('terms.blockchain') }}
+
+ {{ $t('terms.assets') }}
- {{ $t(`blockchains.${accountSetup.configuration.blockchain}.name`) }}
+ {{ assetsText }}
@@ -24,6 +24,7 @@ import AccountSetupWizard, {
import { useDefaultAccountSetupWizardModel } from '~/composables/account.composable';
import { AddAccountOperation, Request } from '~/generated/station/station.did';
import RequestOperationListRow from '../RequestOperationListRow.vue';
+import { useStationStore } from '~/stores/station.store';
const props = withDefaults(
defineProps<{
@@ -35,15 +36,22 @@ const props = withDefaults(
mode: 'list',
},
);
-
+const station = useStationStore();
const isListMode = computed(() => props.mode === 'list');
const accountSetup: Ref = ref(useDefaultAccountSetupWizardModel());
+const assetsText = computed(() =>
+ props.operation.input.assets
+ .map(id => station.configuration.details.supported_assets.find(asset => asset.id === id))
+ .filter(a => !!a)
+ .map(asset => `${asset.name} (${asset.symbol})`)
+ .join(', '),
+);
+
onBeforeMount(() => {
const model: AccountSetupWizardModel = useDefaultAccountSetupWizardModel();
model.configuration.name = props.operation.input.name;
- model.configuration.blockchain = props.operation.input.blockchain;
- model.configuration.standard = props.operation.input.standard;
+ model.configuration.assets = props.operation.input.assets;
model.request_policy.configurationRule = props.operation.input.configs_request_policy?.[0];
model.request_policy.transferRule = props.operation.input.transfer_request_policy?.[0];
model.permission.configuration = props.operation.input.configs_permission;
diff --git a/apps/wallet/src/components/requests/operations/AddAssetOperation.vue b/apps/wallet/src/components/requests/operations/AddAssetOperation.vue
new file mode 100644
index 000000000..70d914cd6
--- /dev/null
+++ b/apps/wallet/src/components/requests/operations/AddAssetOperation.vue
@@ -0,0 +1,52 @@
+
+
+
+ {{ $t('terms.symbol') }}
+ {{ formValue.symbol }} ({{ formValue.name }})
+
+
+ {{ $t('terms.standards') }}
+
+ {{
+ formValue.standards
+ ?.map(standard => $t(`blockchains.${formValue.blockchain!}.standards.${standard}`))
+ .join(', ')
+ }}
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/requests/operations/EditAccountOperation.vue b/apps/wallet/src/components/requests/operations/EditAccountOperation.vue
index ccdf4c51f..9d07f71db 100644
--- a/apps/wallet/src/components/requests/operations/EditAccountOperation.vue
+++ b/apps/wallet/src/components/requests/operations/EditAccountOperation.vue
@@ -6,6 +6,22 @@
{{ props.operation.input.name[0] ?? '-' }}
+
+ {{ $t('terms.assets') }}
+
+
+ {{ editAssets.addAssets }}
+
+
+
+ {{ editAssets.removeAssets }}
+
+
+
+ {{ editAssets.replaceAssets }}
+
+
+
@@ -23,8 +39,10 @@ import {
} from '~/composables/account.composable';
import logger from '~/core/logger.core';
import { EditAccountOperation, Request } from '~/generated/station/station.did';
-import { variantIs } from '~/utils/helper.utils';
+import { unreachable, variantIs } from '~/utils/helper.utils';
import RequestOperationListRow from '../RequestOperationListRow.vue';
+import { useI18n } from 'vue-i18n';
+import { useStationStore } from '~/stores/station.store';
const props = withDefaults(
defineProps<{
@@ -37,10 +55,60 @@ const props = withDefaults(
},
);
+const i18n = useI18n();
+
const isListMode = computed(() => props.mode === 'list');
const model: Ref = ref(useDefaultAccountSetupWizardModel());
const loading = ref(false);
+const editAssets = computed(() => {
+ const assets = {
+ addAssets: '',
+ replaceAssets: '',
+ removeAssets: '',
+ };
+ if (props.operation.input.change_assets[0]) {
+ if (variantIs(props.operation.input.change_assets[0], 'Change')) {
+ if (props.operation.input.change_assets[0].Change.add_assets.length > 0) {
+ assets.addAssets = `${i18n.t('requests.types.editaccount.added_assets')}: ${assetIdsToString(
+ props.operation.input.change_assets[0].Change.add_assets,
+ )}`;
+ }
+
+ if (props.operation.input.change_assets[0].Change.remove_assets.length > 0) {
+ assets.removeAssets = `${i18n.t('requests.types.editaccount.removed_assets')}: ${assetIdsToString(
+ props.operation.input.change_assets[0].Change.remove_assets,
+ )}`;
+ }
+ } else if (variantIs(props.operation.input.change_assets[0], 'ReplaceWith')) {
+ assets.replaceAssets = `${i18n.t('requests.types.editaccount.replaced_assets')}: ${assetIdsToString(
+ props.operation.input.change_assets[0].ReplaceWith.assets,
+ )}`;
+ } else {
+ unreachable(props.operation.input.change_assets[0]);
+ }
+ }
+
+ return assets;
+});
+
+const station = useStationStore();
+
+function assetIdsToString(ids: string[]): string {
+ return ids
+ .map(id => {
+ const maybeAsset = station.configuration.details.supported_assets.find(
+ asset => asset.id == id,
+ );
+ if (maybeAsset) {
+ return `${maybeAsset.symbol} (${maybeAsset.name})`;
+ } else {
+ return id;
+ }
+ })
+ .join(', ');
+}
+
const fetchDetails = async () => {
try {
if (loading.value || isListMode.value) {
diff --git a/apps/wallet/src/components/requests/operations/EditAddressBookEntryOperation.vue b/apps/wallet/src/components/requests/operations/EditAddressBookEntryOperation.vue
index 8feda4535..79d1a5ed4 100644
--- a/apps/wallet/src/components/requests/operations/EditAddressBookEntryOperation.vue
+++ b/apps/wallet/src/components/requests/operations/EditAddressBookEntryOperation.vue
@@ -19,7 +19,7 @@
-
+
@@ -35,6 +35,7 @@ import {
import { useStationStore } from '~/stores/station.store';
import { variantIs } from '~/utils/helper.utils';
import RequestOperationListRow from '../RequestOperationListRow.vue';
+import { VProgressCircular } from 'vuetify/components';
const props = withDefaults(
defineProps<{
diff --git a/apps/wallet/src/components/requests/operations/EditAssetOperation.vue b/apps/wallet/src/components/requests/operations/EditAssetOperation.vue
new file mode 100644
index 000000000..85b0b78c7
--- /dev/null
+++ b/apps/wallet/src/components/requests/operations/EditAssetOperation.vue
@@ -0,0 +1,140 @@
+
+
+
+ {{ $t('terms.symbol') }}
+
+ {{ symbolName }}
+
+
+
+ {{ $t('terms.standards') }}
+ {{ blockchainStandards }}
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/requests/operations/EditPermissionOperation.vue b/apps/wallet/src/components/requests/operations/EditPermissionOperation.vue
index fca1a296c..277dfd0c0 100644
--- a/apps/wallet/src/components/requests/operations/EditPermissionOperation.vue
+++ b/apps/wallet/src/components/requests/operations/EditPermissionOperation.vue
@@ -11,7 +11,7 @@
-
+
diff --git a/apps/wallet/src/components/requests/operations/RemoveAddressBookEntryOperation.vue b/apps/wallet/src/components/requests/operations/RemoveAddressBookEntryOperation.vue
index 562582380..676312280 100644
--- a/apps/wallet/src/components/requests/operations/RemoveAddressBookEntryOperation.vue
+++ b/apps/wallet/src/components/requests/operations/RemoveAddressBookEntryOperation.vue
@@ -7,7 +7,7 @@
-
+
@@ -22,6 +22,7 @@ import {
} from '~/generated/station/station.did';
import { useStationStore } from '~/stores/station.store';
import RequestOperationListRow from '../RequestOperationListRow.vue';
+import { VProgressCircular } from 'vuetify/components';
const props = withDefaults(
defineProps<{
diff --git a/apps/wallet/src/components/requests/operations/RemoveAssetOperation.vue b/apps/wallet/src/components/requests/operations/RemoveAssetOperation.vue
new file mode 100644
index 000000000..e02a22a9c
--- /dev/null
+++ b/apps/wallet/src/components/requests/operations/RemoveAssetOperation.vue
@@ -0,0 +1,77 @@
+
+
+
+ {{ $t('terms.id') }}
+
+ {{ formValue.id }}
+
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/components/requests/operations/TransferOperation.vue b/apps/wallet/src/components/requests/operations/TransferOperation.vue
index 08542c758..aa23fb3cb 100644
--- a/apps/wallet/src/components/requests/operations/TransferOperation.vue
+++ b/apps/wallet/src/components/requests/operations/TransferOperation.vue
@@ -18,7 +18,7 @@
-
+
- {{ account ? formatBalance(formValue.amount, account.decimals) : '-' }}
- {{ account ? account.symbol : '' }}
+ {{ account ? formatBalance(formValue.amount, asset.decimals) : '-' }}
+ {{ account ? asset.symbol : '' }}
@@ -46,7 +46,7 @@
:prepend-icon="mdiWallet"
readonly
/>
-
+
@@ -60,6 +60,9 @@ import { Routes } from '~/configs/routes.config';
import TextOverflow from '~/components/TextOverflow.vue';
import { copyToClipboard } from '~/utils/app.utils';
import { formatBalance } from '~/utils/helper.utils';
+import ShortenedAddress from '~/components/ShortenedAddress.vue';
+import { AddressFormat } from '~/types/chain.types';
+import { detectAddressFormat } from '~/utils/asset.utils';
const props = withDefaults(
defineProps<{
@@ -75,6 +78,8 @@ const props = withDefaults(
const isListMode = computed(() => props.mode === 'list');
const formValue: Ref> = ref({});
const account = computed(() => props.operation.from_account?.[0]);
+const asset = computed(() => props.operation.from_asset);
+const format = ref(undefined);
onBeforeMount(() => {
const transfer: Partial = {};
@@ -89,6 +94,8 @@ onBeforeMount(() => {
}
transfer.metadata = props.operation.input.metadata;
+ format.value = detectAddressFormat(props.operation.from_asset.blockchain, transfer.to);
+
formValue.value = transfer;
});
diff --git a/apps/wallet/src/composables/account.composable.ts b/apps/wallet/src/composables/account.composable.ts
index 34b7cb944..2699de2e3 100644
--- a/apps/wallet/src/composables/account.composable.ts
+++ b/apps/wallet/src/composables/account.composable.ts
@@ -7,7 +7,6 @@ import logger from '~/core/logger.core';
import { UUID } from '~/generated/station/station.did';
import { useAppStore } from '~/stores/app.store';
import { useStationStore } from '~/stores/station.store';
-import { BlockchainStandard, BlockchainType, TokenSymbol } from '~/types/chain.types';
import { parseDate } from '~/utils/date.utils';
export type Filters = {
@@ -84,9 +83,7 @@ export const useDefaultAccountSetupWizardModel = ({
} = {}): AccountSetupWizardModel => {
return {
configuration: {
- blockchain: BlockchainType.InternetComputer,
- standard: BlockchainStandard.Native,
- symbol: TokenSymbol.ICP,
+ assets: [],
},
permission: {
read: {
@@ -147,10 +144,8 @@ export const useLoadAccountSetupWizardModel = async (
configuration: {
id: account.id,
name: account.name,
- blockchain: account.blockchain,
lastModified: account.last_modification_timestamp,
- standard: account.standard,
- symbol: account.symbol,
+ assets: account.assets.map(accountAsset => accountAsset.asset_id),
},
permission: {
read,
diff --git a/apps/wallet/src/composables/autocomplete.composable.ts b/apps/wallet/src/composables/autocomplete.composable.ts
index ee3161c30..8c6a1e3d8 100644
--- a/apps/wallet/src/composables/autocomplete.composable.ts
+++ b/apps/wallet/src/composables/autocomplete.composable.ts
@@ -101,3 +101,18 @@ export const useAddressBookAutocomplete = () => {
return autocomplete;
};
+
+export const useAssetAutocomplete = () => {
+ const station = useStationStore();
+
+ const autocomplete = useAutocomplete(async () => {
+ const results = await station.service.listAssets({
+ limit: 100,
+ offset: 0,
+ });
+
+ return results.assets;
+ });
+
+ return autocomplete;
+};
diff --git a/apps/wallet/src/composables/request.composable.ts b/apps/wallet/src/composables/request.composable.ts
index ed4f0db9a..356ae8fc5 100644
--- a/apps/wallet/src/composables/request.composable.ts
+++ b/apps/wallet/src/composables/request.composable.ts
@@ -73,6 +73,13 @@ export const useAvailableDomains = (
});
}
+ if (hasRequiredPrivilege({ anyOf: [Privilege.ListAssets] })) {
+ domains.value.push({
+ id: RequestDomains.Assets,
+ types: [{ AddAsset: null }, { EditAsset: null }, { RemoveAsset: null }],
+ });
+ }
+
domains.value.push({
id: RequestDomains.System,
types: [
diff --git a/apps/wallet/src/configs/permissions.config.ts b/apps/wallet/src/configs/permissions.config.ts
index bb9e198fd..2cfbc5011 100644
--- a/apps/wallet/src/configs/permissions.config.ts
+++ b/apps/wallet/src/configs/permissions.config.ts
@@ -364,6 +364,48 @@ export const globalPermissions = (): AggregatedResoucePermissions[] => [
);
}
+ return false;
+ },
+ },
+ {
+ resourceType: ResourceTypeEnum.Asset,
+ resources: [
+ {
+ action: ResourceActionEnum.List,
+ resource: { Asset: { List: null } },
+ allow: defaultAllowLevels(),
+ canEdit: false,
+ },
+ {
+ action: ResourceActionEnum.Create,
+ resource: { Asset: { Create: null } },
+ allow: defaultAllowLevels(),
+ canEdit: false,
+ },
+ {
+ action: ResourceActionEnum.Read,
+ resource: { Asset: { Read: { Any: null } } },
+ allow: defaultAllowLevels(),
+ canEdit: false,
+ },
+ {
+ action: ResourceActionEnum.Update,
+ resource: { Asset: { Update: { Any: null } } },
+ allow: defaultAllowLevels(),
+ canEdit: false,
+ },
+ {
+ action: ResourceActionEnum.Delete,
+ resource: { Asset: { Delete: { Any: null } } },
+ allow: defaultAllowLevels(),
+ canEdit: false,
+ },
+ ],
+ match(specifier: Resource, resource: Resource): boolean {
+ if (variantIs(specifier, 'Asset') && variantIs(resource, 'Asset')) {
+ return isResourceActionContained(specifier.Asset, resource.Asset);
+ }
+
return false;
},
},
diff --git a/apps/wallet/src/configs/request-policies.config.ts b/apps/wallet/src/configs/request-policies.config.ts
index 0400a4b07..c7bcb37cf 100644
--- a/apps/wallet/src/configs/request-policies.config.ts
+++ b/apps/wallet/src/configs/request-policies.config.ts
@@ -39,4 +39,7 @@ export const requestSpecifiersIncludedRules = (): Record<
[RequestSpecifierEnum.CallExternalCanister]: [...defaultRequestPolicyRules],
[RequestSpecifierEnum.FundExternalCanister]: [...defaultRequestPolicyRules],
[RequestSpecifierEnum.SetDisasterRecovery]: [...defaultRequestPolicyRules],
+ [RequestSpecifierEnum.AddAsset]: [...defaultRequestPolicyRules],
+ [RequestSpecifierEnum.EditAsset]: [...defaultRequestPolicyRules],
+ [RequestSpecifierEnum.RemoveAsset]: [...defaultRequestPolicyRules],
});
diff --git a/apps/wallet/src/configs/routes.config.ts b/apps/wallet/src/configs/routes.config.ts
index 29b83872a..bf081c6fd 100644
--- a/apps/wallet/src/configs/routes.config.ts
+++ b/apps/wallet/src/configs/routes.config.ts
@@ -2,8 +2,10 @@ export enum Routes {
Login = 'Login',
Error = 'Error',
NotFound = 'NotFound',
+ Dashboard = 'Dashboard',
Accounts = 'Accounts',
Account = 'Account',
+ AccountAsset = 'AccountAsset',
MySettings = 'MySettings',
UserGroups = 'UserGroups',
SystemSettings = 'SystemSettings',
@@ -14,6 +16,7 @@ export enum Routes {
Initialization = 'Initialization',
AddStation = 'AddStation',
Permissions = 'Permissions',
+ Assets = 'Assets',
ExternalCanisters = 'ExternalCanisters',
ExternalCanister = 'ExternalCanister',
// Request Pages
@@ -31,4 +34,4 @@ export enum RouteStatusCode {
}
export const defaultLoginRoute = Routes.Login;
-export const defaultHomeRoute = Routes.Accounts;
+export const defaultHomeRoute = Routes.Dashboard;
diff --git a/apps/wallet/src/generated/icp_ledger/icp_ledger.did b/apps/wallet/src/generated/icp_ledger/icp_ledger.did
new file mode 100644
index 000000000..c11b9434f
--- /dev/null
+++ b/apps/wallet/src/generated/icp_ledger/icp_ledger.did
@@ -0,0 +1,451 @@
+// This is the official Ledger interface that is guaranteed to be backward compatible.
+
+// Amount of tokens, measured in 10^-8 of a token.
+type Tokens = record {
+ e8s : nat64;
+};
+
+// Number of nanoseconds from the UNIX epoch in UTC timezone.
+type TimeStamp = record {
+ timestamp_nanos: nat64;
+};
+
+// AccountIdentifier is a 32-byte array.
+// The first 4 bytes is big-endian encoding of a CRC32 checksum of the last 28 bytes.
+type AccountIdentifier = blob;
+
+// Subaccount is an arbitrary 32-byte byte array.
+// Ledger uses subaccounts to compute the source address, which enables one
+// principal to control multiple ledger accounts.
+type SubAccount = blob;
+
+// Sequence number of a block produced by the ledger.
+type BlockIndex = nat64;
+
+type Transaction = record {
+ memo : Memo;
+ icrc1_memo: opt blob;
+ operation : opt Operation;
+ created_at_time : TimeStamp;
+};
+
+// An arbitrary number associated with a transaction.
+// The caller can set it in a `transfer` call as a correlation identifier.
+type Memo = nat64;
+
+// Arguments for the `transfer` call.
+type TransferArgs = record {
+ // Transaction memo.
+ // See comments for the `Memo` type.
+ memo: Memo;
+ // The amount that the caller wants to transfer to the destination address.
+ amount: Tokens;
+ // The amount that the caller pays for the transaction.
+ // Must be 10000 e8s.
+ fee: Tokens;
+ // The subaccount from which the caller wants to transfer funds.
+ // If null, the ledger uses the default (all zeros) subaccount to compute the source address.
+ // See comments for the `SubAccount` type.
+ from_subaccount: opt SubAccount;
+ // The destination account.
+ // If the transfer is successful, the balance of this address increases by `amount`.
+ to: AccountIdentifier;
+ // The point in time when the caller created this request.
+ // If null, the ledger uses current IC time as the timestamp.
+ created_at_time: opt TimeStamp;
+};
+
+type TransferError = variant {
+ // The fee that the caller specified in the transfer request was not the one that ledger expects.
+ // The caller can change the transfer fee to the `expected_fee` and retry the request.
+ BadFee : record { expected_fee : Tokens; };
+ // The account specified by the caller doesn't have enough funds.
+ InsufficientFunds : record { balance: Tokens; };
+ // The request is too old.
+ // The ledger only accepts requests created within 24 hours window.
+ // This is a non-recoverable error.
+ TxTooOld : record { allowed_window_nanos: nat64 };
+ // The caller specified `created_at_time` that is too far in future.
+ // The caller can retry the request later.
+ TxCreatedInFuture : null;
+ // The ledger has already executed the request.
+ // `duplicate_of` field is equal to the index of the block containing the original transaction.
+ TxDuplicate : record { duplicate_of: BlockIndex; }
+};
+
+type TransferResult = variant {
+ Ok : BlockIndex;
+ Err : TransferError;
+};
+
+// Arguments for the `account_balance` call.
+type AccountBalanceArgs = record {
+ account: AccountIdentifier;
+};
+
+type TransferFeeArg = record {};
+
+type TransferFee = record {
+ // The fee to pay to perform a transfer
+ transfer_fee: Tokens;
+};
+
+type GetBlocksArgs = record {
+ // The index of the first block to fetch.
+ start : BlockIndex;
+ // Max number of blocks to fetch.
+ length : nat64;
+};
+
+type Operation = variant {
+ Mint : record {
+ to : AccountIdentifier;
+ amount : Tokens;
+ };
+ Burn : record {
+ from : AccountIdentifier;
+ spender : opt AccountIdentifier;
+ amount : Tokens;
+ };
+ Transfer : record {
+ from : AccountIdentifier;
+ to : AccountIdentifier;
+ amount : Tokens;
+ fee : Tokens;
+ };
+ Approve : record {
+ from : AccountIdentifier;
+ spender : AccountIdentifier;
+ // This field is deprecated and should not be used.
+ allowance_e8s : int;
+ allowance: Tokens;
+ fee : Tokens;
+ expires_at : opt TimeStamp;
+ };
+ TransferFrom : record {
+ from : AccountIdentifier;
+ to : AccountIdentifier;
+ spender : AccountIdentifier;
+ amount : Tokens;
+ fee : Tokens;
+ };
+};
+
+
+
+type Block = record {
+ parent_hash : opt blob;
+ transaction : Transaction;
+ timestamp : TimeStamp;
+};
+
+// A prefix of the block range specified in the [GetBlocksArgs] request.
+type BlockRange = record {
+ // A prefix of the requested block range.
+ // The index of the first block is equal to [GetBlocksArgs.from].
+ //
+ // Note that the number of blocks might be less than the requested
+ // [GetBlocksArgs.len] for various reasons, for example:
+ //
+ // 1. The query might have hit the replica with an outdated state
+ // that doesn't have the full block range yet.
+ // 2. The requested range is too large to fit into a single reply.
+ //
+ // NOTE: the list of blocks can be empty if:
+ // 1. [GetBlocksArgs.len] was zero.
+ // 2. [GetBlocksArgs.from] was larger than the last block known to the canister.
+ blocks : vec Block;
+};
+
+// An error indicating that the arguments passed to [QueryArchiveFn] were invalid.
+type QueryArchiveError = variant {
+ // [GetBlocksArgs.from] argument was smaller than the first block
+ // served by the canister that received the request.
+ BadFirstBlockIndex : record {
+ requested_index : BlockIndex;
+ first_valid_index : BlockIndex;
+ };
+
+ // Reserved for future use.
+ Other : record {
+ error_code : nat64;
+ error_message : text;
+ };
+};
+
+type QueryArchiveResult = variant {
+ // Successfully fetched zero or more blocks.
+ Ok : BlockRange;
+ // The [GetBlocksArgs] request was invalid.
+ Err : QueryArchiveError;
+};
+
+// A function that is used for fetching archived ledger blocks.
+type QueryArchiveFn = func (GetBlocksArgs) -> (QueryArchiveResult) query;
+
+// The result of a "query_blocks" call.
+//
+// The structure of the result is somewhat complicated because the main ledger canister might
+// not have all the blocks that the caller requested: One or more "archive" canisters might
+// store some of the requested blocks.
+//
+// Note: as of Q4 2021 when this interface is authored, the IC doesn't support making nested
+// query calls within a query call.
+type QueryBlocksResponse = record {
+ // The total number of blocks in the chain.
+ // If the chain length is positive, the index of the last block is `chain_len - 1`.
+ chain_length : nat64;
+
+ // System certificate for the hash of the latest block in the chain.
+ // Only present if `query_blocks` is called in a non-replicated query context.
+ certificate : opt blob;
+
+ // List of blocks that were available in the ledger when it processed the call.
+ //
+ // The blocks form a contiguous range, with the first block having index
+ // [first_block_index] (see below), and the last block having index
+ // [first_block_index] + len(blocks) - 1.
+ //
+ // The block range can be an arbitrary sub-range of the originally requested range.
+ blocks : vec Block;
+
+ // The index of the first block in "blocks".
+ // If the blocks vector is empty, the exact value of this field is not specified.
+ first_block_index : BlockIndex;
+
+ // Encoding of instructions for fetching archived blocks whose indices fall into the
+ // requested range.
+ //
+ // For each entry `e` in [archived_blocks], `[e.from, e.from + len)` is a sub-range
+ // of the originally requested block range.
+ archived_blocks : vec ArchivedBlocksRange;
+};
+
+type ArchivedBlocksRange = record {
+ // The index of the first archived block that can be fetched using the callback.
+ start : BlockIndex;
+
+ // The number of blocks that can be fetch using the callback.
+ length : nat64;
+
+ // The function that should be called to fetch the archived blocks.
+ // The range of the blocks accessible using this function is given by [from]
+ // and [len] fields above.
+ callback : QueryArchiveFn;
+};
+
+type ArchivedEncodedBlocksRange = record {
+ callback : func (GetBlocksArgs) -> (
+ variant { Ok : vec blob; Err : QueryArchiveError },
+ ) query;
+ start : nat64;
+ length : nat64;
+};
+
+type QueryEncodedBlocksResponse = record {
+ certificate : opt blob;
+ blocks : vec blob;
+ chain_length : nat64;
+ first_block_index : nat64;
+ archived_blocks : vec ArchivedEncodedBlocksRange;
+};
+
+type Archive = record {
+ canister_id: principal;
+};
+
+type Archives = record {
+ archives: vec Archive;
+};
+
+type Duration = record {
+ secs: nat64;
+ nanos: nat32;
+};
+
+type ArchiveOptions = record {
+ trigger_threshold : nat64;
+ num_blocks_to_archive : nat64;
+ node_max_memory_size_bytes: opt nat64;
+ max_message_size_bytes: opt nat64;
+ controller_id: principal;
+ cycles_for_archive_creation: opt nat64;
+};
+
+// Account identifier encoded as a 64-byte ASCII hex string.
+type TextAccountIdentifier = text;
+
+// Arguments for the `send_dfx` call.
+type SendArgs = record {
+ memo: Memo;
+ amount: Tokens;
+ fee: Tokens;
+ from_subaccount: opt SubAccount;
+ to: TextAccountIdentifier;
+ created_at_time: opt TimeStamp;
+};
+
+type AccountBalanceArgsDfx = record {
+ account: TextAccountIdentifier;
+};
+
+type FeatureFlags = record {
+ icrc2 : bool;
+};
+
+type InitArgs = record {
+ minting_account: TextAccountIdentifier;
+ icrc1_minting_account: opt Account;
+ initial_values: vec record {TextAccountIdentifier; Tokens};
+ max_message_size_bytes: opt nat64;
+ transaction_window: opt Duration;
+ archive_options: opt ArchiveOptions;
+ send_whitelist: vec principal;
+ transfer_fee: opt Tokens;
+ token_symbol: opt text;
+ token_name: opt text;
+ feature_flags : opt FeatureFlags;
+ maximum_number_of_accounts : opt nat64;
+ accounts_overflow_trim_quantity: opt nat64;
+};
+
+type Icrc1BlockIndex = nat;
+// Number of nanoseconds since the UNIX epoch in UTC timezone.
+type Icrc1Timestamp = nat64;
+type Icrc1Tokens = nat;
+
+type Account = record {
+ owner : principal;
+ subaccount : opt SubAccount;
+};
+
+type TransferArg = record {
+ from_subaccount : opt SubAccount;
+ to : Account;
+ amount : Icrc1Tokens;
+ fee : opt Icrc1Tokens;
+ memo : opt blob;
+ created_at_time: opt Icrc1Timestamp;
+};
+
+type Icrc1TransferError = variant {
+ BadFee : record { expected_fee : Icrc1Tokens };
+ BadBurn : record { min_burn_amount : Icrc1Tokens };
+ InsufficientFunds : record { balance : Icrc1Tokens };
+ TooOld;
+ CreatedInFuture : record { ledger_time : nat64 };
+ TemporarilyUnavailable;
+ Duplicate : record { duplicate_of : Icrc1BlockIndex };
+ GenericError : record { error_code : nat; message : text };
+};
+
+type Icrc1TransferResult = variant {
+ Ok : Icrc1BlockIndex;
+ Err : Icrc1TransferError;
+};
+
+// The value returned from the [icrc1_metadata] endpoint.
+type Value = variant {
+ Nat : nat;
+ Int : int;
+ Text : text;
+ Blob : blob;
+};
+
+type UpgradeArgs = record {
+ maximum_number_of_accounts : opt nat64;
+ icrc1_minting_account : opt Account;
+ feature_flags : opt FeatureFlags;
+};
+
+type LedgerCanisterPayload = variant {
+ Init: InitArgs;
+ Upgrade: opt UpgradeArgs;
+};
+
+type ApproveArgs = record {
+ from_subaccount : opt SubAccount;
+ spender : Account;
+ amount : Icrc1Tokens;
+ expected_allowance : opt Icrc1Tokens;
+ expires_at : opt TimeStamp;
+ fee : opt Icrc1Tokens;
+ memo : opt blob;
+ created_at_time: opt TimeStamp;
+};
+
+type ApproveError = variant {
+ BadFee : record { expected_fee : Icrc1Tokens };
+ InsufficientFunds : record { balance : Icrc1Tokens };
+ AllowanceChanged : record { current_allowance : Icrc1Tokens };
+ Expired : record { ledger_time : nat64 };
+ TooOld;
+ CreatedInFuture : record { ledger_time : nat64 };
+ Duplicate : record { duplicate_of : Icrc1BlockIndex };
+ TemporarilyUnavailable;
+ GenericError : record { error_code : nat; message : text };
+};
+
+type ApproveResult = variant {
+ Ok : Icrc1BlockIndex;
+ Err : ApproveError;
+};
+
+type AllowanceArgs = record {
+ account : Account;
+ spender : Account;
+};
+
+type Allowance = record {
+ allowance : Icrc1Tokens;
+ expires_at : opt TimeStamp;
+};
+
+service: (LedgerCanisterPayload) -> {
+ // Transfers tokens from a subaccount of the caller to the destination address.
+ // The source address is computed from the principal of the caller and the specified subaccount.
+ // When successful, returns the index of the block containing the transaction.
+ transfer : (TransferArgs) -> (TransferResult);
+
+ // Returns the amount of Tokens on the specified account.
+ account_balance : (AccountBalanceArgs) -> (Tokens) query;
+
+ // Returns the current transfer_fee.
+ transfer_fee : (TransferFeeArg) -> (TransferFee) query;
+
+ // Queries blocks in the specified range.
+ query_blocks : (GetBlocksArgs) -> (QueryBlocksResponse) query;
+
+ // Queries encoded blocks in the specified range
+ query_encoded_blocks : (GetBlocksArgs) -> (QueryEncodedBlocksResponse) query;
+
+ // Returns token symbol.
+ symbol : () -> (record { symbol: text }) query;
+
+ // Returns token name.
+ name : () -> (record { name: text }) query;
+
+ // Returns token decimals.
+ decimals : () -> (record { decimals: nat32 }) query;
+
+ // Returns the existing archive canisters information.
+ archives : () -> (Archives) query;
+
+ send_dfx : (SendArgs) -> (BlockIndex);
+ account_balance_dfx : (AccountBalanceArgsDfx) -> (Tokens) query;
+
+ // The following methods implement the ICRC-1 Token Standard.
+ // https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-1
+ icrc1_name : () -> (text) query;
+ icrc1_symbol : () -> (text) query;
+ icrc1_decimals : () -> (nat8) query;
+ icrc1_metadata : () -> (vec record { text; Value }) query;
+ icrc1_total_supply : () -> (Icrc1Tokens) query;
+ icrc1_fee : () -> (Icrc1Tokens) query;
+ icrc1_minting_account : () -> (opt Account) query;
+ icrc1_balance_of : (Account) -> (Icrc1Tokens) query;
+ icrc1_transfer : (TransferArg) -> (Icrc1TransferResult);
+ icrc1_supported_standards : () -> (vec record { name : text; url : text }) query;
+ icrc2_approve : (ApproveArgs) -> (ApproveResult);
+ icrc2_allowance : (AllowanceArgs) -> (Allowance) query;
+}
diff --git a/apps/wallet/src/generated/icp_ledger/icp_ledger.did.d.ts b/apps/wallet/src/generated/icp_ledger/icp_ledger.did.d.ts
new file mode 100644
index 000000000..420381746
--- /dev/null
+++ b/apps/wallet/src/generated/icp_ledger/icp_ledger.did.d.ts
@@ -0,0 +1,248 @@
+import type { Principal } from '@dfinity/principal';
+import type { ActorMethod } from '@dfinity/agent';
+import type { IDL } from '@dfinity/candid';
+
+export interface Account {
+ 'owner' : Principal,
+ 'subaccount' : [] | [SubAccount],
+}
+export interface AccountBalanceArgs { 'account' : AccountIdentifier }
+export interface AccountBalanceArgsDfx { 'account' : TextAccountIdentifier }
+export type AccountIdentifier = Uint8Array | number[];
+export interface Allowance {
+ 'allowance' : Icrc1Tokens,
+ 'expires_at' : [] | [TimeStamp],
+}
+export interface AllowanceArgs { 'account' : Account, 'spender' : Account }
+export interface ApproveArgs {
+ 'fee' : [] | [Icrc1Tokens],
+ 'memo' : [] | [Uint8Array | number[]],
+ 'from_subaccount' : [] | [SubAccount],
+ 'created_at_time' : [] | [TimeStamp],
+ 'amount' : Icrc1Tokens,
+ 'expected_allowance' : [] | [Icrc1Tokens],
+ 'expires_at' : [] | [TimeStamp],
+ 'spender' : Account,
+}
+export type ApproveError = {
+ 'GenericError' : { 'message' : string, 'error_code' : bigint }
+ } |
+ { 'TemporarilyUnavailable' : null } |
+ { 'Duplicate' : { 'duplicate_of' : Icrc1BlockIndex } } |
+ { 'BadFee' : { 'expected_fee' : Icrc1Tokens } } |
+ { 'AllowanceChanged' : { 'current_allowance' : Icrc1Tokens } } |
+ { 'CreatedInFuture' : { 'ledger_time' : bigint } } |
+ { 'TooOld' : null } |
+ { 'Expired' : { 'ledger_time' : bigint } } |
+ { 'InsufficientFunds' : { 'balance' : Icrc1Tokens } };
+export type ApproveResult = { 'Ok' : Icrc1BlockIndex } |
+ { 'Err' : ApproveError };
+export interface Archive { 'canister_id' : Principal }
+export interface ArchiveOptions {
+ 'num_blocks_to_archive' : bigint,
+ 'trigger_threshold' : bigint,
+ 'max_message_size_bytes' : [] | [bigint],
+ 'cycles_for_archive_creation' : [] | [bigint],
+ 'node_max_memory_size_bytes' : [] | [bigint],
+ 'controller_id' : Principal,
+}
+export interface ArchivedBlocksRange {
+ 'callback' : QueryArchiveFn,
+ 'start' : BlockIndex,
+ 'length' : bigint,
+}
+export interface ArchivedEncodedBlocksRange {
+ 'callback' : [Principal, string],
+ 'start' : bigint,
+ 'length' : bigint,
+}
+export interface Archives { 'archives' : Array }
+export interface Block {
+ 'transaction' : Transaction,
+ 'timestamp' : TimeStamp,
+ 'parent_hash' : [] | [Uint8Array | number[]],
+}
+export type BlockIndex = bigint;
+export interface BlockRange { 'blocks' : Array }
+export interface Duration { 'secs' : bigint, 'nanos' : number }
+export interface FeatureFlags { 'icrc2' : boolean }
+export interface GetBlocksArgs { 'start' : BlockIndex, 'length' : bigint }
+export type Icrc1BlockIndex = bigint;
+export type Icrc1Timestamp = bigint;
+export type Icrc1Tokens = bigint;
+export type Icrc1TransferError = {
+ 'GenericError' : { 'message' : string, 'error_code' : bigint }
+ } |
+ { 'TemporarilyUnavailable' : null } |
+ { 'BadBurn' : { 'min_burn_amount' : Icrc1Tokens } } |
+ { 'Duplicate' : { 'duplicate_of' : Icrc1BlockIndex } } |
+ { 'BadFee' : { 'expected_fee' : Icrc1Tokens } } |
+ { 'CreatedInFuture' : { 'ledger_time' : bigint } } |
+ { 'TooOld' : null } |
+ { 'InsufficientFunds' : { 'balance' : Icrc1Tokens } };
+export type Icrc1TransferResult = { 'Ok' : Icrc1BlockIndex } |
+ { 'Err' : Icrc1TransferError };
+export interface InitArgs {
+ 'send_whitelist' : Array,
+ 'token_symbol' : [] | [string],
+ 'transfer_fee' : [] | [Tokens],
+ 'minting_account' : TextAccountIdentifier,
+ 'maximum_number_of_accounts' : [] | [bigint],
+ 'accounts_overflow_trim_quantity' : [] | [bigint],
+ 'transaction_window' : [] | [Duration],
+ 'max_message_size_bytes' : [] | [bigint],
+ 'icrc1_minting_account' : [] | [Account],
+ 'archive_options' : [] | [ArchiveOptions],
+ 'initial_values' : Array<[TextAccountIdentifier, Tokens]>,
+ 'token_name' : [] | [string],
+ 'feature_flags' : [] | [FeatureFlags],
+}
+export type LedgerCanisterPayload = { 'Upgrade' : [] | [UpgradeArgs] } |
+ { 'Init' : InitArgs };
+export type Memo = bigint;
+export type Operation = {
+ 'Approve' : {
+ 'fee' : Tokens,
+ 'from' : AccountIdentifier,
+ 'allowance_e8s' : bigint,
+ 'allowance' : Tokens,
+ 'expires_at' : [] | [TimeStamp],
+ 'spender' : AccountIdentifier,
+ }
+ } |
+ {
+ 'Burn' : {
+ 'from' : AccountIdentifier,
+ 'amount' : Tokens,
+ 'spender' : [] | [AccountIdentifier],
+ }
+ } |
+ { 'Mint' : { 'to' : AccountIdentifier, 'amount' : Tokens } } |
+ {
+ 'Transfer' : {
+ 'to' : AccountIdentifier,
+ 'fee' : Tokens,
+ 'from' : AccountIdentifier,
+ 'amount' : Tokens,
+ }
+ } |
+ {
+ 'TransferFrom' : {
+ 'to' : AccountIdentifier,
+ 'fee' : Tokens,
+ 'from' : AccountIdentifier,
+ 'amount' : Tokens,
+ 'spender' : AccountIdentifier,
+ }
+ };
+export type QueryArchiveError = {
+ 'BadFirstBlockIndex' : {
+ 'requested_index' : BlockIndex,
+ 'first_valid_index' : BlockIndex,
+ }
+ } |
+ { 'Other' : { 'error_message' : string, 'error_code' : bigint } };
+export type QueryArchiveFn = ActorMethod<[GetBlocksArgs], QueryArchiveResult>;
+export type QueryArchiveResult = { 'Ok' : BlockRange } |
+ { 'Err' : QueryArchiveError };
+export interface QueryBlocksResponse {
+ 'certificate' : [] | [Uint8Array | number[]],
+ 'blocks' : Array,
+ 'chain_length' : bigint,
+ 'first_block_index' : BlockIndex,
+ 'archived_blocks' : Array,
+}
+export interface QueryEncodedBlocksResponse {
+ 'certificate' : [] | [Uint8Array | number[]],
+ 'blocks' : Array,
+ 'chain_length' : bigint,
+ 'first_block_index' : bigint,
+ 'archived_blocks' : Array,
+}
+export interface SendArgs {
+ 'to' : TextAccountIdentifier,
+ 'fee' : Tokens,
+ 'memo' : Memo,
+ 'from_subaccount' : [] | [SubAccount],
+ 'created_at_time' : [] | [TimeStamp],
+ 'amount' : Tokens,
+}
+export type SubAccount = Uint8Array | number[];
+export type TextAccountIdentifier = string;
+export interface TimeStamp { 'timestamp_nanos' : bigint }
+export interface Tokens { 'e8s' : bigint }
+export interface Transaction {
+ 'memo' : Memo,
+ 'icrc1_memo' : [] | [Uint8Array | number[]],
+ 'operation' : [] | [Operation],
+ 'created_at_time' : TimeStamp,
+}
+export interface TransferArg {
+ 'to' : Account,
+ 'fee' : [] | [Icrc1Tokens],
+ 'memo' : [] | [Uint8Array | number[]],
+ 'from_subaccount' : [] | [SubAccount],
+ 'created_at_time' : [] | [Icrc1Timestamp],
+ 'amount' : Icrc1Tokens,
+}
+export interface TransferArgs {
+ 'to' : AccountIdentifier,
+ 'fee' : Tokens,
+ 'memo' : Memo,
+ 'from_subaccount' : [] | [SubAccount],
+ 'created_at_time' : [] | [TimeStamp],
+ 'amount' : Tokens,
+}
+export type TransferError = {
+ 'TxTooOld' : { 'allowed_window_nanos' : bigint }
+ } |
+ { 'BadFee' : { 'expected_fee' : Tokens } } |
+ { 'TxDuplicate' : { 'duplicate_of' : BlockIndex } } |
+ { 'TxCreatedInFuture' : null } |
+ { 'InsufficientFunds' : { 'balance' : Tokens } };
+export interface TransferFee { 'transfer_fee' : Tokens }
+export type TransferFeeArg = {};
+export type TransferResult = { 'Ok' : BlockIndex } |
+ { 'Err' : TransferError };
+export interface UpgradeArgs {
+ 'maximum_number_of_accounts' : [] | [bigint],
+ 'icrc1_minting_account' : [] | [Account],
+ 'feature_flags' : [] | [FeatureFlags],
+}
+export type Value = { 'Int' : bigint } |
+ { 'Nat' : bigint } |
+ { 'Blob' : Uint8Array | number[] } |
+ { 'Text' : string };
+export interface _SERVICE {
+ 'account_balance' : ActorMethod<[AccountBalanceArgs], Tokens>,
+ 'account_balance_dfx' : ActorMethod<[AccountBalanceArgsDfx], Tokens>,
+ 'archives' : ActorMethod<[], Archives>,
+ 'decimals' : ActorMethod<[], { 'decimals' : number }>,
+ 'icrc1_balance_of' : ActorMethod<[Account], Icrc1Tokens>,
+ 'icrc1_decimals' : ActorMethod<[], number>,
+ 'icrc1_fee' : ActorMethod<[], Icrc1Tokens>,
+ 'icrc1_metadata' : ActorMethod<[], Array<[string, Value]>>,
+ 'icrc1_minting_account' : ActorMethod<[], [] | [Account]>,
+ 'icrc1_name' : ActorMethod<[], string>,
+ 'icrc1_supported_standards' : ActorMethod<
+ [],
+ Array<{ 'url' : string, 'name' : string }>
+ >,
+ 'icrc1_symbol' : ActorMethod<[], string>,
+ 'icrc1_total_supply' : ActorMethod<[], Icrc1Tokens>,
+ 'icrc1_transfer' : ActorMethod<[TransferArg], Icrc1TransferResult>,
+ 'icrc2_allowance' : ActorMethod<[AllowanceArgs], Allowance>,
+ 'icrc2_approve' : ActorMethod<[ApproveArgs], ApproveResult>,
+ 'name' : ActorMethod<[], { 'name' : string }>,
+ 'query_blocks' : ActorMethod<[GetBlocksArgs], QueryBlocksResponse>,
+ 'query_encoded_blocks' : ActorMethod<
+ [GetBlocksArgs],
+ QueryEncodedBlocksResponse
+ >,
+ 'send_dfx' : ActorMethod<[SendArgs], BlockIndex>,
+ 'symbol' : ActorMethod<[], { 'symbol' : string }>,
+ 'transfer' : ActorMethod<[TransferArgs], TransferResult>,
+ 'transfer_fee' : ActorMethod<[TransferFeeArg], TransferFee>,
+}
+export declare const idlFactory: IDL.InterfaceFactory;
+export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];
diff --git a/apps/wallet/src/generated/icp_ledger/icp_ledger.did.js b/apps/wallet/src/generated/icp_ledger/icp_ledger.did.js
new file mode 100644
index 000000000..1dc00ee62
--- /dev/null
+++ b/apps/wallet/src/generated/icp_ledger/icp_ledger.did.js
@@ -0,0 +1,342 @@
+export const idlFactory = ({ IDL }) => {
+ const SubAccount = IDL.Vec(IDL.Nat8);
+ const Account = IDL.Record({
+ 'owner' : IDL.Principal,
+ 'subaccount' : IDL.Opt(SubAccount),
+ });
+ const FeatureFlags = IDL.Record({ 'icrc2' : IDL.Bool });
+ const UpgradeArgs = IDL.Record({
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'icrc1_minting_account' : IDL.Opt(Account),
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const Tokens = IDL.Record({ 'e8s' : IDL.Nat64 });
+ const TextAccountIdentifier = IDL.Text;
+ const Duration = IDL.Record({ 'secs' : IDL.Nat64, 'nanos' : IDL.Nat32 });
+ const ArchiveOptions = IDL.Record({
+ 'num_blocks_to_archive' : IDL.Nat64,
+ 'trigger_threshold' : IDL.Nat64,
+ 'max_message_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'cycles_for_archive_creation' : IDL.Opt(IDL.Nat64),
+ 'node_max_memory_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'controller_id' : IDL.Principal,
+ });
+ const InitArgs = IDL.Record({
+ 'send_whitelist' : IDL.Vec(IDL.Principal),
+ 'token_symbol' : IDL.Opt(IDL.Text),
+ 'transfer_fee' : IDL.Opt(Tokens),
+ 'minting_account' : TextAccountIdentifier,
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'accounts_overflow_trim_quantity' : IDL.Opt(IDL.Nat64),
+ 'transaction_window' : IDL.Opt(Duration),
+ 'max_message_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'icrc1_minting_account' : IDL.Opt(Account),
+ 'archive_options' : IDL.Opt(ArchiveOptions),
+ 'initial_values' : IDL.Vec(IDL.Tuple(TextAccountIdentifier, Tokens)),
+ 'token_name' : IDL.Opt(IDL.Text),
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const LedgerCanisterPayload = IDL.Variant({
+ 'Upgrade' : IDL.Opt(UpgradeArgs),
+ 'Init' : InitArgs,
+ });
+ const AccountIdentifier = IDL.Vec(IDL.Nat8);
+ const AccountBalanceArgs = IDL.Record({ 'account' : AccountIdentifier });
+ const AccountBalanceArgsDfx = IDL.Record({
+ 'account' : TextAccountIdentifier,
+ });
+ const Archive = IDL.Record({ 'canister_id' : IDL.Principal });
+ const Archives = IDL.Record({ 'archives' : IDL.Vec(Archive) });
+ const Icrc1Tokens = IDL.Nat;
+ const Value = IDL.Variant({
+ 'Int' : IDL.Int,
+ 'Nat' : IDL.Nat,
+ 'Blob' : IDL.Vec(IDL.Nat8),
+ 'Text' : IDL.Text,
+ });
+ const Icrc1Timestamp = IDL.Nat64;
+ const TransferArg = IDL.Record({
+ 'to' : Account,
+ 'fee' : IDL.Opt(Icrc1Tokens),
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'from_subaccount' : IDL.Opt(SubAccount),
+ 'created_at_time' : IDL.Opt(Icrc1Timestamp),
+ 'amount' : Icrc1Tokens,
+ });
+ const Icrc1BlockIndex = IDL.Nat;
+ const Icrc1TransferError = IDL.Variant({
+ 'GenericError' : IDL.Record({
+ 'message' : IDL.Text,
+ 'error_code' : IDL.Nat,
+ }),
+ 'TemporarilyUnavailable' : IDL.Null,
+ 'BadBurn' : IDL.Record({ 'min_burn_amount' : Icrc1Tokens }),
+ 'Duplicate' : IDL.Record({ 'duplicate_of' : Icrc1BlockIndex }),
+ 'BadFee' : IDL.Record({ 'expected_fee' : Icrc1Tokens }),
+ 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'TooOld' : IDL.Null,
+ 'InsufficientFunds' : IDL.Record({ 'balance' : Icrc1Tokens }),
+ });
+ const Icrc1TransferResult = IDL.Variant({
+ 'Ok' : Icrc1BlockIndex,
+ 'Err' : Icrc1TransferError,
+ });
+ const AllowanceArgs = IDL.Record({
+ 'account' : Account,
+ 'spender' : Account,
+ });
+ const TimeStamp = IDL.Record({ 'timestamp_nanos' : IDL.Nat64 });
+ const Allowance = IDL.Record({
+ 'allowance' : Icrc1Tokens,
+ 'expires_at' : IDL.Opt(TimeStamp),
+ });
+ const ApproveArgs = IDL.Record({
+ 'fee' : IDL.Opt(Icrc1Tokens),
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'from_subaccount' : IDL.Opt(SubAccount),
+ 'created_at_time' : IDL.Opt(TimeStamp),
+ 'amount' : Icrc1Tokens,
+ 'expected_allowance' : IDL.Opt(Icrc1Tokens),
+ 'expires_at' : IDL.Opt(TimeStamp),
+ 'spender' : Account,
+ });
+ const ApproveError = IDL.Variant({
+ 'GenericError' : IDL.Record({
+ 'message' : IDL.Text,
+ 'error_code' : IDL.Nat,
+ }),
+ 'TemporarilyUnavailable' : IDL.Null,
+ 'Duplicate' : IDL.Record({ 'duplicate_of' : Icrc1BlockIndex }),
+ 'BadFee' : IDL.Record({ 'expected_fee' : Icrc1Tokens }),
+ 'AllowanceChanged' : IDL.Record({ 'current_allowance' : Icrc1Tokens }),
+ 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'TooOld' : IDL.Null,
+ 'Expired' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'InsufficientFunds' : IDL.Record({ 'balance' : Icrc1Tokens }),
+ });
+ const ApproveResult = IDL.Variant({
+ 'Ok' : Icrc1BlockIndex,
+ 'Err' : ApproveError,
+ });
+ const BlockIndex = IDL.Nat64;
+ const GetBlocksArgs = IDL.Record({
+ 'start' : BlockIndex,
+ 'length' : IDL.Nat64,
+ });
+ const Memo = IDL.Nat64;
+ const Operation = IDL.Variant({
+ 'Approve' : IDL.Record({
+ 'fee' : Tokens,
+ 'from' : AccountIdentifier,
+ 'allowance_e8s' : IDL.Int,
+ 'allowance' : Tokens,
+ 'expires_at' : IDL.Opt(TimeStamp),
+ 'spender' : AccountIdentifier,
+ }),
+ 'Burn' : IDL.Record({
+ 'from' : AccountIdentifier,
+ 'amount' : Tokens,
+ 'spender' : IDL.Opt(AccountIdentifier),
+ }),
+ 'Mint' : IDL.Record({ 'to' : AccountIdentifier, 'amount' : Tokens }),
+ 'Transfer' : IDL.Record({
+ 'to' : AccountIdentifier,
+ 'fee' : Tokens,
+ 'from' : AccountIdentifier,
+ 'amount' : Tokens,
+ }),
+ 'TransferFrom' : IDL.Record({
+ 'to' : AccountIdentifier,
+ 'fee' : Tokens,
+ 'from' : AccountIdentifier,
+ 'amount' : Tokens,
+ 'spender' : AccountIdentifier,
+ }),
+ });
+ const Transaction = IDL.Record({
+ 'memo' : Memo,
+ 'icrc1_memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'operation' : IDL.Opt(Operation),
+ 'created_at_time' : TimeStamp,
+ });
+ const Block = IDL.Record({
+ 'transaction' : Transaction,
+ 'timestamp' : TimeStamp,
+ 'parent_hash' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ });
+ const BlockRange = IDL.Record({ 'blocks' : IDL.Vec(Block) });
+ const QueryArchiveError = IDL.Variant({
+ 'BadFirstBlockIndex' : IDL.Record({
+ 'requested_index' : BlockIndex,
+ 'first_valid_index' : BlockIndex,
+ }),
+ 'Other' : IDL.Record({
+ 'error_message' : IDL.Text,
+ 'error_code' : IDL.Nat64,
+ }),
+ });
+ const QueryArchiveResult = IDL.Variant({
+ 'Ok' : BlockRange,
+ 'Err' : QueryArchiveError,
+ });
+ const QueryArchiveFn = IDL.Func(
+ [GetBlocksArgs],
+ [QueryArchiveResult],
+ ['query'],
+ );
+ const ArchivedBlocksRange = IDL.Record({
+ 'callback' : QueryArchiveFn,
+ 'start' : BlockIndex,
+ 'length' : IDL.Nat64,
+ });
+ const QueryBlocksResponse = IDL.Record({
+ 'certificate' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'blocks' : IDL.Vec(Block),
+ 'chain_length' : IDL.Nat64,
+ 'first_block_index' : BlockIndex,
+ 'archived_blocks' : IDL.Vec(ArchivedBlocksRange),
+ });
+ const ArchivedEncodedBlocksRange = IDL.Record({
+ 'callback' : IDL.Func(
+ [GetBlocksArgs],
+ [
+ IDL.Variant({
+ 'Ok' : IDL.Vec(IDL.Vec(IDL.Nat8)),
+ 'Err' : QueryArchiveError,
+ }),
+ ],
+ ['query'],
+ ),
+ 'start' : IDL.Nat64,
+ 'length' : IDL.Nat64,
+ });
+ const QueryEncodedBlocksResponse = IDL.Record({
+ 'certificate' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'blocks' : IDL.Vec(IDL.Vec(IDL.Nat8)),
+ 'chain_length' : IDL.Nat64,
+ 'first_block_index' : IDL.Nat64,
+ 'archived_blocks' : IDL.Vec(ArchivedEncodedBlocksRange),
+ });
+ const SendArgs = IDL.Record({
+ 'to' : TextAccountIdentifier,
+ 'fee' : Tokens,
+ 'memo' : Memo,
+ 'from_subaccount' : IDL.Opt(SubAccount),
+ 'created_at_time' : IDL.Opt(TimeStamp),
+ 'amount' : Tokens,
+ });
+ const TransferArgs = IDL.Record({
+ 'to' : AccountIdentifier,
+ 'fee' : Tokens,
+ 'memo' : Memo,
+ 'from_subaccount' : IDL.Opt(SubAccount),
+ 'created_at_time' : IDL.Opt(TimeStamp),
+ 'amount' : Tokens,
+ });
+ const TransferError = IDL.Variant({
+ 'TxTooOld' : IDL.Record({ 'allowed_window_nanos' : IDL.Nat64 }),
+ 'BadFee' : IDL.Record({ 'expected_fee' : Tokens }),
+ 'TxDuplicate' : IDL.Record({ 'duplicate_of' : BlockIndex }),
+ 'TxCreatedInFuture' : IDL.Null,
+ 'InsufficientFunds' : IDL.Record({ 'balance' : Tokens }),
+ });
+ const TransferResult = IDL.Variant({
+ 'Ok' : BlockIndex,
+ 'Err' : TransferError,
+ });
+ const TransferFeeArg = IDL.Record({});
+ const TransferFee = IDL.Record({ 'transfer_fee' : Tokens });
+ return IDL.Service({
+ 'account_balance' : IDL.Func([AccountBalanceArgs], [Tokens], ['query']),
+ 'account_balance_dfx' : IDL.Func(
+ [AccountBalanceArgsDfx],
+ [Tokens],
+ ['query'],
+ ),
+ 'archives' : IDL.Func([], [Archives], ['query']),
+ 'decimals' : IDL.Func(
+ [],
+ [IDL.Record({ 'decimals' : IDL.Nat32 })],
+ ['query'],
+ ),
+ 'icrc1_balance_of' : IDL.Func([Account], [Icrc1Tokens], ['query']),
+ 'icrc1_decimals' : IDL.Func([], [IDL.Nat8], ['query']),
+ 'icrc1_fee' : IDL.Func([], [Icrc1Tokens], ['query']),
+ 'icrc1_metadata' : IDL.Func(
+ [],
+ [IDL.Vec(IDL.Tuple(IDL.Text, Value))],
+ ['query'],
+ ),
+ 'icrc1_minting_account' : IDL.Func([], [IDL.Opt(Account)], ['query']),
+ 'icrc1_name' : IDL.Func([], [IDL.Text], ['query']),
+ 'icrc1_supported_standards' : IDL.Func(
+ [],
+ [IDL.Vec(IDL.Record({ 'url' : IDL.Text, 'name' : IDL.Text }))],
+ ['query'],
+ ),
+ 'icrc1_symbol' : IDL.Func([], [IDL.Text], ['query']),
+ 'icrc1_total_supply' : IDL.Func([], [Icrc1Tokens], ['query']),
+ 'icrc1_transfer' : IDL.Func([TransferArg], [Icrc1TransferResult], []),
+ 'icrc2_allowance' : IDL.Func([AllowanceArgs], [Allowance], ['query']),
+ 'icrc2_approve' : IDL.Func([ApproveArgs], [ApproveResult], []),
+ 'name' : IDL.Func([], [IDL.Record({ 'name' : IDL.Text })], ['query']),
+ 'query_blocks' : IDL.Func(
+ [GetBlocksArgs],
+ [QueryBlocksResponse],
+ ['query'],
+ ),
+ 'query_encoded_blocks' : IDL.Func(
+ [GetBlocksArgs],
+ [QueryEncodedBlocksResponse],
+ ['query'],
+ ),
+ 'send_dfx' : IDL.Func([SendArgs], [BlockIndex], []),
+ 'symbol' : IDL.Func([], [IDL.Record({ 'symbol' : IDL.Text })], ['query']),
+ 'transfer' : IDL.Func([TransferArgs], [TransferResult], []),
+ 'transfer_fee' : IDL.Func([TransferFeeArg], [TransferFee], ['query']),
+ });
+};
+export const init = ({ IDL }) => {
+ const SubAccount = IDL.Vec(IDL.Nat8);
+ const Account = IDL.Record({
+ 'owner' : IDL.Principal,
+ 'subaccount' : IDL.Opt(SubAccount),
+ });
+ const FeatureFlags = IDL.Record({ 'icrc2' : IDL.Bool });
+ const UpgradeArgs = IDL.Record({
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'icrc1_minting_account' : IDL.Opt(Account),
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const Tokens = IDL.Record({ 'e8s' : IDL.Nat64 });
+ const TextAccountIdentifier = IDL.Text;
+ const Duration = IDL.Record({ 'secs' : IDL.Nat64, 'nanos' : IDL.Nat32 });
+ const ArchiveOptions = IDL.Record({
+ 'num_blocks_to_archive' : IDL.Nat64,
+ 'trigger_threshold' : IDL.Nat64,
+ 'max_message_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'cycles_for_archive_creation' : IDL.Opt(IDL.Nat64),
+ 'node_max_memory_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'controller_id' : IDL.Principal,
+ });
+ const InitArgs = IDL.Record({
+ 'send_whitelist' : IDL.Vec(IDL.Principal),
+ 'token_symbol' : IDL.Opt(IDL.Text),
+ 'transfer_fee' : IDL.Opt(Tokens),
+ 'minting_account' : TextAccountIdentifier,
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'accounts_overflow_trim_quantity' : IDL.Opt(IDL.Nat64),
+ 'transaction_window' : IDL.Opt(Duration),
+ 'max_message_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'icrc1_minting_account' : IDL.Opt(Account),
+ 'archive_options' : IDL.Opt(ArchiveOptions),
+ 'initial_values' : IDL.Vec(IDL.Tuple(TextAccountIdentifier, Tokens)),
+ 'token_name' : IDL.Opt(IDL.Text),
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const LedgerCanisterPayload = IDL.Variant({
+ 'Upgrade' : IDL.Opt(UpgradeArgs),
+ 'Init' : InitArgs,
+ });
+ return [LedgerCanisterPayload];
+};
diff --git a/apps/wallet/src/generated/icp_ledger/index.d.ts b/apps/wallet/src/generated/icp_ledger/index.d.ts
new file mode 100644
index 000000000..513ca9e74
--- /dev/null
+++ b/apps/wallet/src/generated/icp_ledger/index.d.ts
@@ -0,0 +1,50 @@
+import type {
+ ActorSubclass,
+ HttpAgentOptions,
+ ActorConfig,
+ Agent,
+} from "@dfinity/agent";
+import type { Principal } from "@dfinity/principal";
+import type { IDL } from "@dfinity/candid";
+
+import { _SERVICE } from './icp_ledger.did';
+
+export declare const idlFactory: IDL.InterfaceFactory;
+export declare const canisterId: string;
+
+export declare interface CreateActorOptions {
+ /**
+ * @see {@link Agent}
+ */
+ agent?: Agent;
+ /**
+ * @see {@link HttpAgentOptions}
+ */
+ agentOptions?: HttpAgentOptions;
+ /**
+ * @see {@link ActorConfig}
+ */
+ actorOptions?: ActorConfig;
+}
+
+/**
+ * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
+ * @constructs {@link ActorSubClass}
+ * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to
+ * @param {CreateActorOptions} options - see {@link CreateActorOptions}
+ * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions
+ * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent
+ * @see {@link HttpAgentOptions}
+ * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor
+ * @see {@link ActorConfig}
+ */
+export declare const createActor: (
+ canisterId: string | Principal,
+ options?: CreateActorOptions
+) => ActorSubclass<_SERVICE>;
+
+/**
+ * Intialized Actor using default settings, ready to talk to a canister using its candid interface
+ * @constructs {@link ActorSubClass}
+ */
+export declare const icp_ledger: ActorSubclass<_SERVICE>;
diff --git a/apps/wallet/src/generated/icp_ledger/index.js b/apps/wallet/src/generated/icp_ledger/index.js
new file mode 100644
index 000000000..e0f474543
--- /dev/null
+++ b/apps/wallet/src/generated/icp_ledger/index.js
@@ -0,0 +1,40 @@
+import { Actor, HttpAgent } from "@dfinity/agent";
+
+// Imports and re-exports candid interface
+import { idlFactory } from "./icp_ledger.did.js";
+export { idlFactory } from "./icp_ledger.did.js";
+
+/* CANISTER_ID is replaced by webpack based on node environment
+ * Note: canister environment variable will be standardized as
+ * process.env.CANISTER_ID_
+ * beginning in dfx 0.15.0
+ */
+export const canisterId =
+ process.env.CANISTER_ID_ICP_LEDGER;
+
+export const createActor = (canisterId, options = {}) => {
+ const agent = options.agent || new HttpAgent({ ...options.agentOptions });
+
+ if (options.agent && options.agentOptions) {
+ console.warn(
+ "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent."
+ );
+ }
+
+ // Fetch root key for certificate validation during development
+ if (process.env.DFX_NETWORK !== "ic") {
+ agent.fetchRootKey().catch((err) => {
+ console.warn(
+ "Unable to fetch root key. Check to ensure that your local replica is running"
+ );
+ console.error(err);
+ });
+ }
+
+ // Creates an actor with using the candid interface and the HttpAgent
+ return Actor.createActor(idlFactory, {
+ agent,
+ canisterId,
+ ...options.actorOptions,
+ });
+};
diff --git a/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did b/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did
new file mode 100644
index 000000000..5cc7429a6
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did
@@ -0,0 +1,143 @@
+type Tokens = nat;
+
+type InitArg = record {
+ ledger_id: principal;
+};
+
+type UpgradeArg = record {
+ ledger_id: opt principal;
+};
+
+type IndexArg = variant {
+ Init: InitArg;
+ Upgrade: UpgradeArg;
+};
+
+type GetBlocksRequest = record {
+ start : nat;
+ length : nat;
+};
+
+type Value = variant {
+ Blob : blob;
+ Text : text;
+ Nat : nat;
+ Nat64: nat64;
+ Int : int;
+ Array : vec Value;
+ Map : Map;
+};
+
+type Map = vec record { text; Value };
+
+type Block = Value;
+
+type GetBlocksResponse = record {
+ chain_length: nat64;
+ blocks: vec Block;
+};
+
+type BlockIndex = nat;
+
+type SubAccount = blob;
+
+type Account = record { owner : principal; subaccount : opt SubAccount };
+
+type Transaction = record {
+ burn : opt Burn;
+ kind : text;
+ mint : opt Mint;
+ approve : opt Approve;
+ timestamp : nat64;
+ transfer : opt Transfer;
+};
+
+type Approve = record {
+ fee : opt nat;
+ from : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ expected_allowance : opt nat;
+ expires_at : opt nat64;
+ spender : Account;
+};
+
+type Burn = record {
+ from : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ spender : opt Account;
+};
+
+type Mint = record {
+ to : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+};
+
+type Transfer = record {
+ to : Account;
+ fee : opt nat;
+ from : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ spender : opt Account;
+};
+
+type GetAccountTransactionsArgs = record {
+ account : Account;
+ // The txid of the last transaction seen by the client.
+ // If None then the results will start from the most recent
+ // txid.
+ start : opt BlockIndex;
+ // Maximum number of transactions to fetch.
+ max_results : nat;
+};
+
+type TransactionWithId = record {
+ id : BlockIndex;
+ transaction : Transaction;
+};
+
+type GetTransactions = record {
+ balance : Tokens;
+ transactions : vec TransactionWithId;
+ // The txid of the oldest transaction the account has
+ oldest_tx_id : opt BlockIndex;
+};
+
+type GetTransactionsErr = record {
+ message : text;
+};
+
+type GetTransactionsResult = variant {
+ Ok : GetTransactions;
+ Err : GetTransactionsErr;
+};
+
+type ListSubaccountsArgs = record {
+ owner: principal;
+ start: opt SubAccount;
+};
+
+type Status = record {
+ num_blocks_synced : BlockIndex;
+};
+
+type FeeCollectorRanges = record {
+ ranges : vec record { Account; vec record { BlockIndex; BlockIndex } };
+}
+
+service : (index_arg: opt IndexArg) -> {
+ get_account_transactions : (GetAccountTransactionsArgs) -> (GetTransactionsResult) query;
+ get_blocks : (GetBlocksRequest) -> (GetBlocksResponse) query;
+ get_fee_collectors_ranges : () -> (FeeCollectorRanges) query;
+ icrc1_balance_of : (Account) -> (Tokens) query;
+ ledger_id : () -> (principal) query;
+ list_subaccounts : (ListSubaccountsArgs) -> (vec SubAccount) query;
+ status : () -> (Status) query;
+}
diff --git a/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did.d.ts b/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did.d.ts
new file mode 100644
index 000000000..9a93a9a71
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did.d.ts
@@ -0,0 +1,108 @@
+import type { Principal } from '@dfinity/principal';
+import type { ActorMethod } from '@dfinity/agent';
+import type { IDL } from '@dfinity/candid';
+
+export interface Account {
+ 'owner' : Principal,
+ 'subaccount' : [] | [SubAccount],
+}
+export interface Approve {
+ 'fee' : [] | [bigint],
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'expected_allowance' : [] | [bigint],
+ 'expires_at' : [] | [bigint],
+ 'spender' : Account,
+}
+export type Block = Value;
+export type BlockIndex = bigint;
+export interface Burn {
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'spender' : [] | [Account],
+}
+export interface FeeCollectorRanges {
+ 'ranges' : Array<[Account, Array<[BlockIndex, BlockIndex]>]>,
+}
+export interface GetAccountTransactionsArgs {
+ 'max_results' : bigint,
+ 'start' : [] | [BlockIndex],
+ 'account' : Account,
+}
+export interface GetBlocksRequest { 'start' : bigint, 'length' : bigint }
+export interface GetBlocksResponse {
+ 'blocks' : Array,
+ 'chain_length' : bigint,
+}
+export interface GetTransactions {
+ 'balance' : Tokens,
+ 'transactions' : Array,
+ 'oldest_tx_id' : [] | [BlockIndex],
+}
+export interface GetTransactionsErr { 'message' : string }
+export type GetTransactionsResult = { 'Ok' : GetTransactions } |
+ { 'Err' : GetTransactionsErr };
+export type IndexArg = { 'Upgrade' : UpgradeArg } |
+ { 'Init' : InitArg };
+export interface InitArg { 'ledger_id' : Principal }
+export interface ListSubaccountsArgs {
+ 'owner' : Principal,
+ 'start' : [] | [SubAccount],
+}
+export type Map = Array<[string, Value]>;
+export interface Mint {
+ 'to' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+}
+export interface Status { 'num_blocks_synced' : BlockIndex }
+export type SubAccount = Uint8Array | number[];
+export type Tokens = bigint;
+export interface Transaction {
+ 'burn' : [] | [Burn],
+ 'kind' : string,
+ 'mint' : [] | [Mint],
+ 'approve' : [] | [Approve],
+ 'timestamp' : bigint,
+ 'transfer' : [] | [Transfer],
+}
+export interface TransactionWithId {
+ 'id' : BlockIndex,
+ 'transaction' : Transaction,
+}
+export interface Transfer {
+ 'to' : Account,
+ 'fee' : [] | [bigint],
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'spender' : [] | [Account],
+}
+export interface UpgradeArg { 'ledger_id' : [] | [Principal] }
+export type Value = { 'Int' : bigint } |
+ { 'Map' : Map } |
+ { 'Nat' : bigint } |
+ { 'Nat64' : bigint } |
+ { 'Blob' : Uint8Array | number[] } |
+ { 'Text' : string } |
+ { 'Array' : Array };
+export interface _SERVICE {
+ 'get_account_transactions' : ActorMethod<
+ [GetAccountTransactionsArgs],
+ GetTransactionsResult
+ >,
+ 'get_blocks' : ActorMethod<[GetBlocksRequest], GetBlocksResponse>,
+ 'get_fee_collectors_ranges' : ActorMethod<[], FeeCollectorRanges>,
+ 'icrc1_balance_of' : ActorMethod<[Account], Tokens>,
+ 'ledger_id' : ActorMethod<[], Principal>,
+ 'list_subaccounts' : ActorMethod<[ListSubaccountsArgs], Array>,
+ 'status' : ActorMethod<[], Status>,
+}
+export declare const idlFactory: IDL.InterfaceFactory;
+export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];
diff --git a/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did.js b/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did.js
new file mode 100644
index 000000000..0374e7632
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_index/icrc1_index_canister.did.js
@@ -0,0 +1,126 @@
+export const idlFactory = ({ IDL }) => {
+ const Value = IDL.Rec();
+ const UpgradeArg = IDL.Record({ 'ledger_id' : IDL.Opt(IDL.Principal) });
+ const InitArg = IDL.Record({ 'ledger_id' : IDL.Principal });
+ const IndexArg = IDL.Variant({ 'Upgrade' : UpgradeArg, 'Init' : InitArg });
+ const BlockIndex = IDL.Nat;
+ const SubAccount = IDL.Vec(IDL.Nat8);
+ const Account = IDL.Record({
+ 'owner' : IDL.Principal,
+ 'subaccount' : IDL.Opt(SubAccount),
+ });
+ const GetAccountTransactionsArgs = IDL.Record({
+ 'max_results' : IDL.Nat,
+ 'start' : IDL.Opt(BlockIndex),
+ 'account' : Account,
+ });
+ const Tokens = IDL.Nat;
+ const Burn = IDL.Record({
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'spender' : IDL.Opt(Account),
+ });
+ const Mint = IDL.Record({
+ 'to' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ });
+ const Approve = IDL.Record({
+ 'fee' : IDL.Opt(IDL.Nat),
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'expected_allowance' : IDL.Opt(IDL.Nat),
+ 'expires_at' : IDL.Opt(IDL.Nat64),
+ 'spender' : Account,
+ });
+ const Transfer = IDL.Record({
+ 'to' : Account,
+ 'fee' : IDL.Opt(IDL.Nat),
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'spender' : IDL.Opt(Account),
+ });
+ const Transaction = IDL.Record({
+ 'burn' : IDL.Opt(Burn),
+ 'kind' : IDL.Text,
+ 'mint' : IDL.Opt(Mint),
+ 'approve' : IDL.Opt(Approve),
+ 'timestamp' : IDL.Nat64,
+ 'transfer' : IDL.Opt(Transfer),
+ });
+ const TransactionWithId = IDL.Record({
+ 'id' : BlockIndex,
+ 'transaction' : Transaction,
+ });
+ const GetTransactions = IDL.Record({
+ 'balance' : Tokens,
+ 'transactions' : IDL.Vec(TransactionWithId),
+ 'oldest_tx_id' : IDL.Opt(BlockIndex),
+ });
+ const GetTransactionsErr = IDL.Record({ 'message' : IDL.Text });
+ const GetTransactionsResult = IDL.Variant({
+ 'Ok' : GetTransactions,
+ 'Err' : GetTransactionsErr,
+ });
+ const GetBlocksRequest = IDL.Record({
+ 'start' : IDL.Nat,
+ 'length' : IDL.Nat,
+ });
+ const Map = IDL.Vec(IDL.Tuple(IDL.Text, Value));
+ Value.fill(
+ IDL.Variant({
+ 'Int' : IDL.Int,
+ 'Map' : Map,
+ 'Nat' : IDL.Nat,
+ 'Nat64' : IDL.Nat64,
+ 'Blob' : IDL.Vec(IDL.Nat8),
+ 'Text' : IDL.Text,
+ 'Array' : IDL.Vec(Value),
+ })
+ );
+ const Block = Value;
+ const GetBlocksResponse = IDL.Record({
+ 'blocks' : IDL.Vec(Block),
+ 'chain_length' : IDL.Nat64,
+ });
+ const FeeCollectorRanges = IDL.Record({
+ 'ranges' : IDL.Vec(
+ IDL.Tuple(Account, IDL.Vec(IDL.Tuple(BlockIndex, BlockIndex)))
+ ),
+ });
+ const ListSubaccountsArgs = IDL.Record({
+ 'owner' : IDL.Principal,
+ 'start' : IDL.Opt(SubAccount),
+ });
+ const Status = IDL.Record({ 'num_blocks_synced' : BlockIndex });
+ return IDL.Service({
+ 'get_account_transactions' : IDL.Func(
+ [GetAccountTransactionsArgs],
+ [GetTransactionsResult],
+ ['query'],
+ ),
+ 'get_blocks' : IDL.Func([GetBlocksRequest], [GetBlocksResponse], ['query']),
+ 'get_fee_collectors_ranges' : IDL.Func([], [FeeCollectorRanges], ['query']),
+ 'icrc1_balance_of' : IDL.Func([Account], [Tokens], ['query']),
+ 'ledger_id' : IDL.Func([], [IDL.Principal], ['query']),
+ 'list_subaccounts' : IDL.Func(
+ [ListSubaccountsArgs],
+ [IDL.Vec(SubAccount)],
+ ['query'],
+ ),
+ 'status' : IDL.Func([], [Status], ['query']),
+ });
+};
+export const init = ({ IDL }) => {
+ const UpgradeArg = IDL.Record({ 'ledger_id' : IDL.Opt(IDL.Principal) });
+ const InitArg = IDL.Record({ 'ledger_id' : IDL.Principal });
+ const IndexArg = IDL.Variant({ 'Upgrade' : UpgradeArg, 'Init' : InitArg });
+ return [IDL.Opt(IndexArg)];
+};
diff --git a/apps/wallet/src/generated/icrc1_index/index.d.ts b/apps/wallet/src/generated/icrc1_index/index.d.ts
new file mode 100644
index 000000000..b5d550620
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_index/index.d.ts
@@ -0,0 +1,50 @@
+import type {
+ ActorSubclass,
+ HttpAgentOptions,
+ ActorConfig,
+ Agent,
+} from "@dfinity/agent";
+import type { Principal } from "@dfinity/principal";
+import type { IDL } from "@dfinity/candid";
+
+import { _SERVICE } from './icrc1_index_canister.did';
+
+export declare const idlFactory: IDL.InterfaceFactory;
+export declare const canisterId: string;
+
+export declare interface CreateActorOptions {
+ /**
+ * @see {@link Agent}
+ */
+ agent?: Agent;
+ /**
+ * @see {@link HttpAgentOptions}
+ */
+ agentOptions?: HttpAgentOptions;
+ /**
+ * @see {@link ActorConfig}
+ */
+ actorOptions?: ActorConfig;
+}
+
+/**
+ * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
+ * @constructs {@link ActorSubClass}
+ * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to
+ * @param {CreateActorOptions} options - see {@link CreateActorOptions}
+ * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions
+ * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent
+ * @see {@link HttpAgentOptions}
+ * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor
+ * @see {@link ActorConfig}
+ */
+export declare const createActor: (
+ canisterId: string | Principal,
+ options?: CreateActorOptions
+) => ActorSubclass<_SERVICE>;
+
+/**
+ * Intialized Actor using default settings, ready to talk to a canister using its candid interface
+ * @constructs {@link ActorSubClass}
+ */
+export declare const icrc1_index_canister: ActorSubclass<_SERVICE>;
diff --git a/apps/wallet/src/generated/icrc1_index/index.js b/apps/wallet/src/generated/icrc1_index/index.js
new file mode 100644
index 000000000..084e136d6
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_index/index.js
@@ -0,0 +1,40 @@
+import { Actor, HttpAgent } from "@dfinity/agent";
+
+// Imports and re-exports candid interface
+import { idlFactory } from "./icrc1_index_canister.did.js";
+export { idlFactory } from "./icrc1_index_canister.did.js";
+
+/* CANISTER_ID is replaced by webpack based on node environment
+ * Note: canister environment variable will be standardized as
+ * process.env.CANISTER_ID_
+ * beginning in dfx 0.15.0
+ */
+export const canisterId =
+ process.env.CANISTER_ID_ICRC1_INDEX_CANISTER;
+
+export const createActor = (canisterId, options = {}) => {
+ const agent = options.agent || new HttpAgent({ ...options.agentOptions });
+
+ if (options.agent && options.agentOptions) {
+ console.warn(
+ "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent."
+ );
+ }
+
+ // Fetch root key for certificate validation during development
+ if (process.env.DFX_NETWORK !== "ic") {
+ agent.fetchRootKey().catch((err) => {
+ console.warn(
+ "Unable to fetch root key. Check to ensure that your local replica is running"
+ );
+ console.error(err);
+ });
+ }
+
+ // Creates an actor with using the candid interface and the HttpAgent
+ return Actor.createActor(idlFactory, {
+ agent,
+ canisterId,
+ ...options.actorOptions,
+ });
+};
diff --git a/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did b/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did
new file mode 100644
index 000000000..1efe7f6fb
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did
@@ -0,0 +1,379 @@
+type BlockIndex = nat;
+type Subaccount = blob;
+// Number of nanoseconds since the UNIX epoch in UTC timezone.
+type Timestamp = nat64;
+// Number of nanoseconds between two [Timestamp]s.
+type Duration = nat64;
+type Tokens = nat;
+type TxIndex = nat;
+type Allowance = record { allowance : nat; expires_at : opt nat64 };
+type AllowanceArgs = record { account : Account; spender : Account };
+type Approve = record {
+ fee : opt nat;
+ from : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ expected_allowance : opt nat;
+ expires_at : opt nat64;
+ spender : Account;
+};
+type ApproveArgs = record {
+ fee : opt nat;
+ memo : opt vec nat8;
+ from_subaccount : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ expected_allowance : opt nat;
+ expires_at : opt nat64;
+ spender : Account;
+};
+type ApproveError = variant {
+ GenericError : record { message : text; error_code : nat };
+ TemporarilyUnavailable;
+ Duplicate : record { duplicate_of : nat };
+ BadFee : record { expected_fee : nat };
+ AllowanceChanged : record { current_allowance : nat };
+ CreatedInFuture : record { ledger_time : nat64 };
+ TooOld;
+ Expired : record { ledger_time : nat64 };
+ InsufficientFunds : record { balance : nat };
+};
+type ApproveResult = variant { Ok : nat; Err : ApproveError };
+
+type HttpRequest = record {
+ url : text;
+ method : text;
+ body : vec nat8;
+ headers : vec record { text; text };
+};
+type HttpResponse = record {
+ body : vec nat8;
+ headers : vec record { text; text };
+ status_code : nat16;
+};
+
+type Account = record {
+ owner : principal;
+ subaccount : opt Subaccount;
+};
+
+type TransferArg = record {
+ from_subaccount : opt Subaccount;
+ to : Account;
+ amount : Tokens;
+ fee : opt Tokens;
+ memo : opt blob;
+ created_at_time: opt Timestamp;
+};
+
+type TransferError = variant {
+ BadFee : record { expected_fee : Tokens };
+ BadBurn : record { min_burn_amount : Tokens };
+ InsufficientFunds : record { balance : Tokens };
+ TooOld;
+ CreatedInFuture : record { ledger_time : nat64 };
+ TemporarilyUnavailable;
+ Duplicate : record { duplicate_of : BlockIndex };
+ GenericError : record { error_code : nat; message : text };
+};
+
+type TransferResult = variant {
+ Ok : BlockIndex;
+ Err : TransferError;
+};
+
+// The value returned from the [icrc1_metadata] endpoint.
+type MetadataValue = variant {
+ Nat : nat;
+ Int : int;
+ Text : text;
+ Blob : blob;
+};
+
+type FeatureFlags = record {
+ icrc2 : bool;
+};
+
+// The initialization parameters of the Ledger
+type InitArgs = record {
+ minting_account : Account;
+ fee_collector_account : opt Account;
+ transfer_fee : nat;
+ decimals : opt nat8;
+ max_memo_length : opt nat16;
+ token_symbol : text;
+ token_name : text;
+ metadata : vec record { text; MetadataValue };
+ initial_balances : vec record { Account; nat };
+ feature_flags : opt FeatureFlags;
+ maximum_number_of_accounts : opt nat64;
+ accounts_overflow_trim_quantity : opt nat64;
+ archive_options : record {
+ num_blocks_to_archive : nat64;
+ max_transactions_per_response : opt nat64;
+ trigger_threshold : nat64;
+ max_message_size_bytes : opt nat64;
+ cycles_for_archive_creation : opt nat64;
+ node_max_memory_size_bytes : opt nat64;
+ controller_id : principal;
+ };
+};
+
+type ChangeFeeCollector = variant {
+ Unset; SetTo: Account;
+};
+
+type UpgradeArgs = record {
+ metadata : opt vec record { text; MetadataValue };
+ token_symbol : opt text;
+ token_name : opt text;
+ transfer_fee : opt nat;
+ change_fee_collector : opt ChangeFeeCollector;
+ max_memo_length : opt nat16;
+ feature_flags : opt FeatureFlags;
+ maximum_number_of_accounts: opt nat64;
+ accounts_overflow_trim_quantity: opt nat64;
+};
+
+type LedgerArg = variant {
+ Init: InitArgs;
+ Upgrade: opt UpgradeArgs;
+};
+
+type GetTransactionsRequest = record {
+ // The index of the first tx to fetch.
+ start : TxIndex;
+ // The number of transactions to fetch.
+ length : nat;
+};
+
+type GetTransactionsResponse = record {
+ // The total number of transactions in the log.
+ log_length : nat;
+
+ // List of transaction that were available in the ledger when it processed the call.
+ //
+ // The transactions form a contiguous range, with the first transaction having index
+ // [first_index] (see below), and the last transaction having index
+ // [first_index] + len(transactions) - 1.
+ //
+ // The transaction range can be an arbitrary sub-range of the originally requested range.
+ transactions : vec Transaction;
+
+ // The index of the first transaction in [transactions].
+ // If the transaction vector is empty, the exact value of this field is not specified.
+ first_index : TxIndex;
+
+ // Encoding of instructions for fetching archived transactions whose indices fall into the
+ // requested range.
+ //
+ // For each entry `e` in [archived_transactions], `[e.from, e.from + len)` is a sub-range
+ // of the originally requested transaction range.
+ archived_transactions : vec record {
+ // The index of the first archived transaction you can fetch using the [callback].
+ start : TxIndex;
+
+ // The number of transactions you can fetch using the callback.
+ length : nat;
+
+ // The function you should call to fetch the archived transactions.
+ // The range of the transaction accessible using this function is given by [from]
+ // and [len] fields above.
+ callback : QueryArchiveFn;
+ };
+};
+
+
+// A prefix of the transaction range specified in the [GetTransactionsRequest] request.
+type TransactionRange = record {
+ // A prefix of the requested transaction range.
+ // The index of the first transaction is equal to [GetTransactionsRequest.from].
+ //
+ // Note that the number of transactions might be less than the requested
+ // [GetTransactionsRequest.length] for various reasons, for example:
+ //
+ // 1. The query might have hit the replica with an outdated state
+ // that doesn't have the whole range yet.
+ // 2. The requested range is too large to fit into a single reply.
+ //
+ // NOTE: the list of transactions can be empty if:
+ //
+ // 1. [GetTransactionsRequest.length] was zero.
+ // 2. [GetTransactionsRequest.from] was larger than the last transaction known to
+ // the canister.
+ transactions : vec Transaction;
+};
+
+// A function for fetching archived transaction.
+type QueryArchiveFn = func (GetTransactionsRequest) -> (TransactionRange) query;
+
+type Transaction = record {
+ burn : opt Burn;
+ kind : text;
+ mint : opt Mint;
+ approve : opt Approve;
+ timestamp : nat64;
+ transfer : opt Transfer;
+};
+
+type Burn = record {
+ from : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ spender : opt Account;
+};
+
+type Mint = record {
+ to : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+};
+
+type Transfer = record {
+ to : Account;
+ fee : opt nat;
+ from : Account;
+ memo : opt vec nat8;
+ created_at_time : opt nat64;
+ amount : nat;
+ spender : opt Account;
+};
+
+type Value = variant {
+ Blob : blob;
+ Text : text;
+ Nat : nat;
+ Nat64: nat64;
+ Int : int;
+ Array : vec Value;
+ Map : Map;
+};
+
+type Map = vec record { text; Value };
+
+type Block = Value;
+
+type GetBlocksArgs = record {
+ // The index of the first block to fetch.
+ start : BlockIndex;
+ // Max number of blocks to fetch.
+ length : nat;
+};
+
+// A prefix of the block range specified in the [GetBlocksArgs] request.
+type BlockRange = record {
+ // A prefix of the requested block range.
+ // The index of the first block is equal to [GetBlocksArgs.start].
+ //
+ // Note that the number of blocks might be less than the requested
+ // [GetBlocksArgs.length] for various reasons, for example:
+ //
+ // 1. The query might have hit the replica with an outdated state
+ // that doesn't have the whole range yet.
+ // 2. The requested range is too large to fit into a single reply.
+ //
+ // NOTE: the list of blocks can be empty if:
+ //
+ // 1. [GetBlocksArgs.length] was zero.
+ // 2. [GetBlocksArgs.start] was larger than the last block known to
+ // the canister.
+ blocks : vec Block;
+};
+
+// A function for fetching archived blocks.
+type QueryBlockArchiveFn = func (GetBlocksArgs) -> (BlockRange) query;
+
+// The result of a "get_blocks" call.
+type GetBlocksResponse = record {
+ // The index of the first block in "blocks".
+ // If the blocks vector is empty, the exact value of this field is not specified.
+ first_index : BlockIndex;
+
+ // The total number of blocks in the chain.
+ // If the chain length is positive, the index of the last block is `chain_len - 1`.
+ chain_length : nat64;
+
+ // System certificate for the hash of the latest block in the chain.
+ // Only present if `get_blocks` is called in a non-replicated query context.
+ certificate : opt blob;
+
+ // List of blocks that were available in the ledger when it processed the call.
+ //
+ // The blocks form a contiguous range, with the first block having index
+ // [first_block_index] (see below), and the last block having index
+ // [first_block_index] + len(blocks) - 1.
+ //
+ // The block range can be an arbitrary sub-range of the originally requested range.
+ blocks : vec Block;
+
+ // Encoding of instructions for fetching archived blocks.
+ archived_blocks : vec record {
+ // The index of the first archived block.
+ start : BlockIndex;
+
+ // The number of blocks that can be fetched.
+ length : nat;
+
+ // Callback to fetch the archived blocks.
+ callback : QueryBlockArchiveFn;
+ };
+};
+
+// Certificate for the block at `block_index`.
+type DataCertificate = record {
+ certificate : opt blob;
+ hash_tree : blob;
+};
+
+type StandardRecord = record { url : text; name : text };
+
+type TransferFromArgs = record {
+ spender_subaccount : opt Subaccount;
+ from : Account;
+ to : Account;
+ amount : Tokens;
+ fee : opt Tokens;
+ memo : opt blob;
+ created_at_time: opt Timestamp;
+};
+
+type TransferFromResult = variant {
+ Ok : BlockIndex;
+ Err : TransferFromError;
+};
+
+type TransferFromError = variant {
+ BadFee : record { expected_fee : Tokens };
+ BadBurn : record { min_burn_amount : Tokens };
+ InsufficientFunds : record { balance : Tokens };
+ InsufficientAllowance : record { allowance : Tokens };
+ TooOld;
+ CreatedInFuture : record { ledger_time : nat64 };
+ Duplicate : record { duplicate_of : BlockIndex };
+ TemporarilyUnavailable;
+ GenericError : record { error_code : nat; message : text };
+};
+
+service : (ledger_arg : LedgerArg) -> {
+ get_transactions : (GetTransactionsRequest) -> (GetTransactionsResponse) query;
+ get_blocks : (GetBlocksArgs) -> (GetBlocksResponse) query;
+ get_data_certificate : () -> (DataCertificate) query;
+
+ icrc1_name : () -> (text) query;
+ icrc1_symbol : () -> (text) query;
+ icrc1_decimals : () -> (nat8) query;
+ icrc1_metadata : () -> (vec record { text; MetadataValue }) query;
+ icrc1_total_supply : () -> (Tokens) query;
+ icrc1_fee : () -> (Tokens) query;
+ icrc1_minting_account : () -> (opt Account) query;
+ icrc1_balance_of : (Account) -> (Tokens) query;
+ icrc1_transfer : (TransferArg) -> (TransferResult);
+ icrc1_supported_standards : () -> (vec StandardRecord) query;
+
+ icrc2_approve : (ApproveArgs) -> (ApproveResult);
+ icrc2_allowance : (AllowanceArgs) -> (Allowance) query;
+ icrc2_transfer_from : (TransferFromArgs) -> (TransferFromResult);
+}
diff --git a/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did.d.ts b/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did.d.ts
new file mode 100644
index 000000000..5a6ef4b78
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did.d.ts
@@ -0,0 +1,245 @@
+import type { Principal } from '@dfinity/principal';
+import type { ActorMethod } from '@dfinity/agent';
+import type { IDL } from '@dfinity/candid';
+
+export interface Account {
+ 'owner' : Principal,
+ 'subaccount' : [] | [Subaccount],
+}
+export interface Allowance {
+ 'allowance' : bigint,
+ 'expires_at' : [] | [bigint],
+}
+export interface AllowanceArgs { 'account' : Account, 'spender' : Account }
+export interface Approve {
+ 'fee' : [] | [bigint],
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'expected_allowance' : [] | [bigint],
+ 'expires_at' : [] | [bigint],
+ 'spender' : Account,
+}
+export interface ApproveArgs {
+ 'fee' : [] | [bigint],
+ 'memo' : [] | [Uint8Array | number[]],
+ 'from_subaccount' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'expected_allowance' : [] | [bigint],
+ 'expires_at' : [] | [bigint],
+ 'spender' : Account,
+}
+export type ApproveError = {
+ 'GenericError' : { 'message' : string, 'error_code' : bigint }
+ } |
+ { 'TemporarilyUnavailable' : null } |
+ { 'Duplicate' : { 'duplicate_of' : bigint } } |
+ { 'BadFee' : { 'expected_fee' : bigint } } |
+ { 'AllowanceChanged' : { 'current_allowance' : bigint } } |
+ { 'CreatedInFuture' : { 'ledger_time' : bigint } } |
+ { 'TooOld' : null } |
+ { 'Expired' : { 'ledger_time' : bigint } } |
+ { 'InsufficientFunds' : { 'balance' : bigint } };
+export type ApproveResult = { 'Ok' : bigint } |
+ { 'Err' : ApproveError };
+export type Block = Value;
+export type BlockIndex = bigint;
+export interface BlockRange { 'blocks' : Array }
+export interface Burn {
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'spender' : [] | [Account],
+}
+export type ChangeFeeCollector = { 'SetTo' : Account } |
+ { 'Unset' : null };
+export interface DataCertificate {
+ 'certificate' : [] | [Uint8Array | number[]],
+ 'hash_tree' : Uint8Array | number[],
+}
+export type Duration = bigint;
+export interface FeatureFlags { 'icrc2' : boolean }
+export interface GetBlocksArgs { 'start' : BlockIndex, 'length' : bigint }
+export interface GetBlocksResponse {
+ 'certificate' : [] | [Uint8Array | number[]],
+ 'first_index' : BlockIndex,
+ 'blocks' : Array,
+ 'chain_length' : bigint,
+ 'archived_blocks' : Array<
+ {
+ 'callback' : QueryBlockArchiveFn,
+ 'start' : BlockIndex,
+ 'length' : bigint,
+ }
+ >,
+}
+export interface GetTransactionsRequest { 'start' : TxIndex, 'length' : bigint }
+export interface GetTransactionsResponse {
+ 'first_index' : TxIndex,
+ 'log_length' : bigint,
+ 'transactions' : Array,
+ 'archived_transactions' : Array<
+ { 'callback' : QueryArchiveFn, 'start' : TxIndex, 'length' : bigint }
+ >,
+}
+export interface HttpRequest {
+ 'url' : string,
+ 'method' : string,
+ 'body' : Uint8Array | number[],
+ 'headers' : Array<[string, string]>,
+}
+export interface HttpResponse {
+ 'body' : Uint8Array | number[],
+ 'headers' : Array<[string, string]>,
+ 'status_code' : number,
+}
+export interface InitArgs {
+ 'decimals' : [] | [number],
+ 'token_symbol' : string,
+ 'transfer_fee' : bigint,
+ 'metadata' : Array<[string, MetadataValue]>,
+ 'minting_account' : Account,
+ 'initial_balances' : Array<[Account, bigint]>,
+ 'maximum_number_of_accounts' : [] | [bigint],
+ 'accounts_overflow_trim_quantity' : [] | [bigint],
+ 'fee_collector_account' : [] | [Account],
+ 'archive_options' : {
+ 'num_blocks_to_archive' : bigint,
+ 'max_transactions_per_response' : [] | [bigint],
+ 'trigger_threshold' : bigint,
+ 'max_message_size_bytes' : [] | [bigint],
+ 'cycles_for_archive_creation' : [] | [bigint],
+ 'node_max_memory_size_bytes' : [] | [bigint],
+ 'controller_id' : Principal,
+ },
+ 'max_memo_length' : [] | [number],
+ 'token_name' : string,
+ 'feature_flags' : [] | [FeatureFlags],
+}
+export type LedgerArg = { 'Upgrade' : [] | [UpgradeArgs] } |
+ { 'Init' : InitArgs };
+export type Map = Array<[string, Value]>;
+export type MetadataValue = { 'Int' : bigint } |
+ { 'Nat' : bigint } |
+ { 'Blob' : Uint8Array | number[] } |
+ { 'Text' : string };
+export interface Mint {
+ 'to' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+}
+export type QueryArchiveFn = ActorMethod<
+ [GetTransactionsRequest],
+ TransactionRange
+>;
+export type QueryBlockArchiveFn = ActorMethod<[GetBlocksArgs], BlockRange>;
+export interface StandardRecord { 'url' : string, 'name' : string }
+export type Subaccount = Uint8Array | number[];
+export type Timestamp = bigint;
+export type Tokens = bigint;
+export interface Transaction {
+ 'burn' : [] | [Burn],
+ 'kind' : string,
+ 'mint' : [] | [Mint],
+ 'approve' : [] | [Approve],
+ 'timestamp' : bigint,
+ 'transfer' : [] | [Transfer],
+}
+export interface TransactionRange { 'transactions' : Array }
+export interface Transfer {
+ 'to' : Account,
+ 'fee' : [] | [bigint],
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [bigint],
+ 'amount' : bigint,
+ 'spender' : [] | [Account],
+}
+export interface TransferArg {
+ 'to' : Account,
+ 'fee' : [] | [Tokens],
+ 'memo' : [] | [Uint8Array | number[]],
+ 'from_subaccount' : [] | [Subaccount],
+ 'created_at_time' : [] | [Timestamp],
+ 'amount' : Tokens,
+}
+export type TransferError = {
+ 'GenericError' : { 'message' : string, 'error_code' : bigint }
+ } |
+ { 'TemporarilyUnavailable' : null } |
+ { 'BadBurn' : { 'min_burn_amount' : Tokens } } |
+ { 'Duplicate' : { 'duplicate_of' : BlockIndex } } |
+ { 'BadFee' : { 'expected_fee' : Tokens } } |
+ { 'CreatedInFuture' : { 'ledger_time' : bigint } } |
+ { 'TooOld' : null } |
+ { 'InsufficientFunds' : { 'balance' : Tokens } };
+export interface TransferFromArgs {
+ 'to' : Account,
+ 'fee' : [] | [Tokens],
+ 'spender_subaccount' : [] | [Subaccount],
+ 'from' : Account,
+ 'memo' : [] | [Uint8Array | number[]],
+ 'created_at_time' : [] | [Timestamp],
+ 'amount' : Tokens,
+}
+export type TransferFromError = {
+ 'GenericError' : { 'message' : string, 'error_code' : bigint }
+ } |
+ { 'TemporarilyUnavailable' : null } |
+ { 'InsufficientAllowance' : { 'allowance' : Tokens } } |
+ { 'BadBurn' : { 'min_burn_amount' : Tokens } } |
+ { 'Duplicate' : { 'duplicate_of' : BlockIndex } } |
+ { 'BadFee' : { 'expected_fee' : Tokens } } |
+ { 'CreatedInFuture' : { 'ledger_time' : bigint } } |
+ { 'TooOld' : null } |
+ { 'InsufficientFunds' : { 'balance' : Tokens } };
+export type TransferFromResult = { 'Ok' : BlockIndex } |
+ { 'Err' : TransferFromError };
+export type TransferResult = { 'Ok' : BlockIndex } |
+ { 'Err' : TransferError };
+export type TxIndex = bigint;
+export interface UpgradeArgs {
+ 'token_symbol' : [] | [string],
+ 'transfer_fee' : [] | [bigint],
+ 'metadata' : [] | [Array<[string, MetadataValue]>],
+ 'maximum_number_of_accounts' : [] | [bigint],
+ 'accounts_overflow_trim_quantity' : [] | [bigint],
+ 'change_fee_collector' : [] | [ChangeFeeCollector],
+ 'max_memo_length' : [] | [number],
+ 'token_name' : [] | [string],
+ 'feature_flags' : [] | [FeatureFlags],
+}
+export type Value = { 'Int' : bigint } |
+ { 'Map' : Map } |
+ { 'Nat' : bigint } |
+ { 'Nat64' : bigint } |
+ { 'Blob' : Uint8Array | number[] } |
+ { 'Text' : string } |
+ { 'Array' : Array };
+export interface _SERVICE {
+ 'get_blocks' : ActorMethod<[GetBlocksArgs], GetBlocksResponse>,
+ 'get_data_certificate' : ActorMethod<[], DataCertificate>,
+ 'get_transactions' : ActorMethod<
+ [GetTransactionsRequest],
+ GetTransactionsResponse
+ >,
+ 'icrc1_balance_of' : ActorMethod<[Account], Tokens>,
+ 'icrc1_decimals' : ActorMethod<[], number>,
+ 'icrc1_fee' : ActorMethod<[], Tokens>,
+ 'icrc1_metadata' : ActorMethod<[], Array<[string, MetadataValue]>>,
+ 'icrc1_minting_account' : ActorMethod<[], [] | [Account]>,
+ 'icrc1_name' : ActorMethod<[], string>,
+ 'icrc1_supported_standards' : ActorMethod<[], Array>,
+ 'icrc1_symbol' : ActorMethod<[], string>,
+ 'icrc1_total_supply' : ActorMethod<[], Tokens>,
+ 'icrc1_transfer' : ActorMethod<[TransferArg], TransferResult>,
+ 'icrc2_allowance' : ActorMethod<[AllowanceArgs], Allowance>,
+ 'icrc2_approve' : ActorMethod<[ApproveArgs], ApproveResult>,
+ 'icrc2_transfer_from' : ActorMethod<[TransferFromArgs], TransferFromResult>,
+}
+export declare const idlFactory: IDL.InterfaceFactory;
+export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];
diff --git a/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did.js b/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did.js
new file mode 100644
index 000000000..d5c422e15
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_ledger/icrc1_ledger_canister.did.js
@@ -0,0 +1,342 @@
+export const idlFactory = ({ IDL }) => {
+ const Value = IDL.Rec();
+ const MetadataValue = IDL.Variant({
+ 'Int' : IDL.Int,
+ 'Nat' : IDL.Nat,
+ 'Blob' : IDL.Vec(IDL.Nat8),
+ 'Text' : IDL.Text,
+ });
+ const Subaccount = IDL.Vec(IDL.Nat8);
+ const Account = IDL.Record({
+ 'owner' : IDL.Principal,
+ 'subaccount' : IDL.Opt(Subaccount),
+ });
+ const ChangeFeeCollector = IDL.Variant({
+ 'SetTo' : Account,
+ 'Unset' : IDL.Null,
+ });
+ const FeatureFlags = IDL.Record({ 'icrc2' : IDL.Bool });
+ const UpgradeArgs = IDL.Record({
+ 'token_symbol' : IDL.Opt(IDL.Text),
+ 'transfer_fee' : IDL.Opt(IDL.Nat),
+ 'metadata' : IDL.Opt(IDL.Vec(IDL.Tuple(IDL.Text, MetadataValue))),
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'accounts_overflow_trim_quantity' : IDL.Opt(IDL.Nat64),
+ 'change_fee_collector' : IDL.Opt(ChangeFeeCollector),
+ 'max_memo_length' : IDL.Opt(IDL.Nat16),
+ 'token_name' : IDL.Opt(IDL.Text),
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const InitArgs = IDL.Record({
+ 'decimals' : IDL.Opt(IDL.Nat8),
+ 'token_symbol' : IDL.Text,
+ 'transfer_fee' : IDL.Nat,
+ 'metadata' : IDL.Vec(IDL.Tuple(IDL.Text, MetadataValue)),
+ 'minting_account' : Account,
+ 'initial_balances' : IDL.Vec(IDL.Tuple(Account, IDL.Nat)),
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'accounts_overflow_trim_quantity' : IDL.Opt(IDL.Nat64),
+ 'fee_collector_account' : IDL.Opt(Account),
+ 'archive_options' : IDL.Record({
+ 'num_blocks_to_archive' : IDL.Nat64,
+ 'max_transactions_per_response' : IDL.Opt(IDL.Nat64),
+ 'trigger_threshold' : IDL.Nat64,
+ 'max_message_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'cycles_for_archive_creation' : IDL.Opt(IDL.Nat64),
+ 'node_max_memory_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'controller_id' : IDL.Principal,
+ }),
+ 'max_memo_length' : IDL.Opt(IDL.Nat16),
+ 'token_name' : IDL.Text,
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const LedgerArg = IDL.Variant({
+ 'Upgrade' : IDL.Opt(UpgradeArgs),
+ 'Init' : InitArgs,
+ });
+ const BlockIndex = IDL.Nat;
+ const GetBlocksArgs = IDL.Record({
+ 'start' : BlockIndex,
+ 'length' : IDL.Nat,
+ });
+ const Map = IDL.Vec(IDL.Tuple(IDL.Text, Value));
+ Value.fill(
+ IDL.Variant({
+ 'Int' : IDL.Int,
+ 'Map' : Map,
+ 'Nat' : IDL.Nat,
+ 'Nat64' : IDL.Nat64,
+ 'Blob' : IDL.Vec(IDL.Nat8),
+ 'Text' : IDL.Text,
+ 'Array' : IDL.Vec(Value),
+ })
+ );
+ const Block = Value;
+ const BlockRange = IDL.Record({ 'blocks' : IDL.Vec(Block) });
+ const QueryBlockArchiveFn = IDL.Func(
+ [GetBlocksArgs],
+ [BlockRange],
+ ['query'],
+ );
+ const GetBlocksResponse = IDL.Record({
+ 'certificate' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'first_index' : BlockIndex,
+ 'blocks' : IDL.Vec(Block),
+ 'chain_length' : IDL.Nat64,
+ 'archived_blocks' : IDL.Vec(
+ IDL.Record({
+ 'callback' : QueryBlockArchiveFn,
+ 'start' : BlockIndex,
+ 'length' : IDL.Nat,
+ })
+ ),
+ });
+ const DataCertificate = IDL.Record({
+ 'certificate' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'hash_tree' : IDL.Vec(IDL.Nat8),
+ });
+ const TxIndex = IDL.Nat;
+ const GetTransactionsRequest = IDL.Record({
+ 'start' : TxIndex,
+ 'length' : IDL.Nat,
+ });
+ const Burn = IDL.Record({
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'spender' : IDL.Opt(Account),
+ });
+ const Mint = IDL.Record({
+ 'to' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ });
+ const Approve = IDL.Record({
+ 'fee' : IDL.Opt(IDL.Nat),
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'expected_allowance' : IDL.Opt(IDL.Nat),
+ 'expires_at' : IDL.Opt(IDL.Nat64),
+ 'spender' : Account,
+ });
+ const Transfer = IDL.Record({
+ 'to' : Account,
+ 'fee' : IDL.Opt(IDL.Nat),
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'spender' : IDL.Opt(Account),
+ });
+ const Transaction = IDL.Record({
+ 'burn' : IDL.Opt(Burn),
+ 'kind' : IDL.Text,
+ 'mint' : IDL.Opt(Mint),
+ 'approve' : IDL.Opt(Approve),
+ 'timestamp' : IDL.Nat64,
+ 'transfer' : IDL.Opt(Transfer),
+ });
+ const TransactionRange = IDL.Record({
+ 'transactions' : IDL.Vec(Transaction),
+ });
+ const QueryArchiveFn = IDL.Func(
+ [GetTransactionsRequest],
+ [TransactionRange],
+ ['query'],
+ );
+ const GetTransactionsResponse = IDL.Record({
+ 'first_index' : TxIndex,
+ 'log_length' : IDL.Nat,
+ 'transactions' : IDL.Vec(Transaction),
+ 'archived_transactions' : IDL.Vec(
+ IDL.Record({
+ 'callback' : QueryArchiveFn,
+ 'start' : TxIndex,
+ 'length' : IDL.Nat,
+ })
+ ),
+ });
+ const Tokens = IDL.Nat;
+ const StandardRecord = IDL.Record({ 'url' : IDL.Text, 'name' : IDL.Text });
+ const Timestamp = IDL.Nat64;
+ const TransferArg = IDL.Record({
+ 'to' : Account,
+ 'fee' : IDL.Opt(Tokens),
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'from_subaccount' : IDL.Opt(Subaccount),
+ 'created_at_time' : IDL.Opt(Timestamp),
+ 'amount' : Tokens,
+ });
+ const TransferError = IDL.Variant({
+ 'GenericError' : IDL.Record({
+ 'message' : IDL.Text,
+ 'error_code' : IDL.Nat,
+ }),
+ 'TemporarilyUnavailable' : IDL.Null,
+ 'BadBurn' : IDL.Record({ 'min_burn_amount' : Tokens }),
+ 'Duplicate' : IDL.Record({ 'duplicate_of' : BlockIndex }),
+ 'BadFee' : IDL.Record({ 'expected_fee' : Tokens }),
+ 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'TooOld' : IDL.Null,
+ 'InsufficientFunds' : IDL.Record({ 'balance' : Tokens }),
+ });
+ const TransferResult = IDL.Variant({
+ 'Ok' : BlockIndex,
+ 'Err' : TransferError,
+ });
+ const AllowanceArgs = IDL.Record({
+ 'account' : Account,
+ 'spender' : Account,
+ });
+ const Allowance = IDL.Record({
+ 'allowance' : IDL.Nat,
+ 'expires_at' : IDL.Opt(IDL.Nat64),
+ });
+ const ApproveArgs = IDL.Record({
+ 'fee' : IDL.Opt(IDL.Nat),
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'from_subaccount' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(IDL.Nat64),
+ 'amount' : IDL.Nat,
+ 'expected_allowance' : IDL.Opt(IDL.Nat),
+ 'expires_at' : IDL.Opt(IDL.Nat64),
+ 'spender' : Account,
+ });
+ const ApproveError = IDL.Variant({
+ 'GenericError' : IDL.Record({
+ 'message' : IDL.Text,
+ 'error_code' : IDL.Nat,
+ }),
+ 'TemporarilyUnavailable' : IDL.Null,
+ 'Duplicate' : IDL.Record({ 'duplicate_of' : IDL.Nat }),
+ 'BadFee' : IDL.Record({ 'expected_fee' : IDL.Nat }),
+ 'AllowanceChanged' : IDL.Record({ 'current_allowance' : IDL.Nat }),
+ 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'TooOld' : IDL.Null,
+ 'Expired' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }),
+ });
+ const ApproveResult = IDL.Variant({ 'Ok' : IDL.Nat, 'Err' : ApproveError });
+ const TransferFromArgs = IDL.Record({
+ 'to' : Account,
+ 'fee' : IDL.Opt(Tokens),
+ 'spender_subaccount' : IDL.Opt(Subaccount),
+ 'from' : Account,
+ 'memo' : IDL.Opt(IDL.Vec(IDL.Nat8)),
+ 'created_at_time' : IDL.Opt(Timestamp),
+ 'amount' : Tokens,
+ });
+ const TransferFromError = IDL.Variant({
+ 'GenericError' : IDL.Record({
+ 'message' : IDL.Text,
+ 'error_code' : IDL.Nat,
+ }),
+ 'TemporarilyUnavailable' : IDL.Null,
+ 'InsufficientAllowance' : IDL.Record({ 'allowance' : Tokens }),
+ 'BadBurn' : IDL.Record({ 'min_burn_amount' : Tokens }),
+ 'Duplicate' : IDL.Record({ 'duplicate_of' : BlockIndex }),
+ 'BadFee' : IDL.Record({ 'expected_fee' : Tokens }),
+ 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }),
+ 'TooOld' : IDL.Null,
+ 'InsufficientFunds' : IDL.Record({ 'balance' : Tokens }),
+ });
+ const TransferFromResult = IDL.Variant({
+ 'Ok' : BlockIndex,
+ 'Err' : TransferFromError,
+ });
+ return IDL.Service({
+ 'get_blocks' : IDL.Func([GetBlocksArgs], [GetBlocksResponse], ['query']),
+ 'get_data_certificate' : IDL.Func([], [DataCertificate], ['query']),
+ 'get_transactions' : IDL.Func(
+ [GetTransactionsRequest],
+ [GetTransactionsResponse],
+ ['query'],
+ ),
+ 'icrc1_balance_of' : IDL.Func([Account], [Tokens], ['query']),
+ 'icrc1_decimals' : IDL.Func([], [IDL.Nat8], ['query']),
+ 'icrc1_fee' : IDL.Func([], [Tokens], ['query']),
+ 'icrc1_metadata' : IDL.Func(
+ [],
+ [IDL.Vec(IDL.Tuple(IDL.Text, MetadataValue))],
+ ['query'],
+ ),
+ 'icrc1_minting_account' : IDL.Func([], [IDL.Opt(Account)], ['query']),
+ 'icrc1_name' : IDL.Func([], [IDL.Text], ['query']),
+ 'icrc1_supported_standards' : IDL.Func(
+ [],
+ [IDL.Vec(StandardRecord)],
+ ['query'],
+ ),
+ 'icrc1_symbol' : IDL.Func([], [IDL.Text], ['query']),
+ 'icrc1_total_supply' : IDL.Func([], [Tokens], ['query']),
+ 'icrc1_transfer' : IDL.Func([TransferArg], [TransferResult], []),
+ 'icrc2_allowance' : IDL.Func([AllowanceArgs], [Allowance], ['query']),
+ 'icrc2_approve' : IDL.Func([ApproveArgs], [ApproveResult], []),
+ 'icrc2_transfer_from' : IDL.Func(
+ [TransferFromArgs],
+ [TransferFromResult],
+ [],
+ ),
+ });
+};
+export const init = ({ IDL }) => {
+ const MetadataValue = IDL.Variant({
+ 'Int' : IDL.Int,
+ 'Nat' : IDL.Nat,
+ 'Blob' : IDL.Vec(IDL.Nat8),
+ 'Text' : IDL.Text,
+ });
+ const Subaccount = IDL.Vec(IDL.Nat8);
+ const Account = IDL.Record({
+ 'owner' : IDL.Principal,
+ 'subaccount' : IDL.Opt(Subaccount),
+ });
+ const ChangeFeeCollector = IDL.Variant({
+ 'SetTo' : Account,
+ 'Unset' : IDL.Null,
+ });
+ const FeatureFlags = IDL.Record({ 'icrc2' : IDL.Bool });
+ const UpgradeArgs = IDL.Record({
+ 'token_symbol' : IDL.Opt(IDL.Text),
+ 'transfer_fee' : IDL.Opt(IDL.Nat),
+ 'metadata' : IDL.Opt(IDL.Vec(IDL.Tuple(IDL.Text, MetadataValue))),
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'accounts_overflow_trim_quantity' : IDL.Opt(IDL.Nat64),
+ 'change_fee_collector' : IDL.Opt(ChangeFeeCollector),
+ 'max_memo_length' : IDL.Opt(IDL.Nat16),
+ 'token_name' : IDL.Opt(IDL.Text),
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const InitArgs = IDL.Record({
+ 'decimals' : IDL.Opt(IDL.Nat8),
+ 'token_symbol' : IDL.Text,
+ 'transfer_fee' : IDL.Nat,
+ 'metadata' : IDL.Vec(IDL.Tuple(IDL.Text, MetadataValue)),
+ 'minting_account' : Account,
+ 'initial_balances' : IDL.Vec(IDL.Tuple(Account, IDL.Nat)),
+ 'maximum_number_of_accounts' : IDL.Opt(IDL.Nat64),
+ 'accounts_overflow_trim_quantity' : IDL.Opt(IDL.Nat64),
+ 'fee_collector_account' : IDL.Opt(Account),
+ 'archive_options' : IDL.Record({
+ 'num_blocks_to_archive' : IDL.Nat64,
+ 'max_transactions_per_response' : IDL.Opt(IDL.Nat64),
+ 'trigger_threshold' : IDL.Nat64,
+ 'max_message_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'cycles_for_archive_creation' : IDL.Opt(IDL.Nat64),
+ 'node_max_memory_size_bytes' : IDL.Opt(IDL.Nat64),
+ 'controller_id' : IDL.Principal,
+ }),
+ 'max_memo_length' : IDL.Opt(IDL.Nat16),
+ 'token_name' : IDL.Text,
+ 'feature_flags' : IDL.Opt(FeatureFlags),
+ });
+ const LedgerArg = IDL.Variant({
+ 'Upgrade' : IDL.Opt(UpgradeArgs),
+ 'Init' : InitArgs,
+ });
+ return [LedgerArg];
+};
diff --git a/apps/wallet/src/generated/icrc1_ledger/index.d.ts b/apps/wallet/src/generated/icrc1_ledger/index.d.ts
new file mode 100644
index 000000000..bff346fcc
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_ledger/index.d.ts
@@ -0,0 +1,50 @@
+import type {
+ ActorSubclass,
+ HttpAgentOptions,
+ ActorConfig,
+ Agent,
+} from "@dfinity/agent";
+import type { Principal } from "@dfinity/principal";
+import type { IDL } from "@dfinity/candid";
+
+import { _SERVICE } from './icrc1_ledger_canister.did';
+
+export declare const idlFactory: IDL.InterfaceFactory;
+export declare const canisterId: string;
+
+export declare interface CreateActorOptions {
+ /**
+ * @see {@link Agent}
+ */
+ agent?: Agent;
+ /**
+ * @see {@link HttpAgentOptions}
+ */
+ agentOptions?: HttpAgentOptions;
+ /**
+ * @see {@link ActorConfig}
+ */
+ actorOptions?: ActorConfig;
+}
+
+/**
+ * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
+ * @constructs {@link ActorSubClass}
+ * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to
+ * @param {CreateActorOptions} options - see {@link CreateActorOptions}
+ * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions
+ * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent
+ * @see {@link HttpAgentOptions}
+ * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor
+ * @see {@link ActorConfig}
+ */
+export declare const createActor: (
+ canisterId: string | Principal,
+ options?: CreateActorOptions
+) => ActorSubclass<_SERVICE>;
+
+/**
+ * Intialized Actor using default settings, ready to talk to a canister using its candid interface
+ * @constructs {@link ActorSubClass}
+ */
+export declare const icrc1_ledger_canister: ActorSubclass<_SERVICE>;
diff --git a/apps/wallet/src/generated/icrc1_ledger/index.js b/apps/wallet/src/generated/icrc1_ledger/index.js
new file mode 100644
index 000000000..a5df8d522
--- /dev/null
+++ b/apps/wallet/src/generated/icrc1_ledger/index.js
@@ -0,0 +1,40 @@
+import { Actor, HttpAgent } from "@dfinity/agent";
+
+// Imports and re-exports candid interface
+import { idlFactory } from "./icrc1_ledger_canister.did.js";
+export { idlFactory } from "./icrc1_ledger_canister.did.js";
+
+/* CANISTER_ID is replaced by webpack based on node environment
+ * Note: canister environment variable will be standardized as
+ * process.env.CANISTER_ID_
+ * beginning in dfx 0.15.0
+ */
+export const canisterId =
+ process.env.CANISTER_ID_ICRC1_LEDGER_CANISTER;
+
+export const createActor = (canisterId, options = {}) => {
+ const agent = options.agent || new HttpAgent({ ...options.agentOptions });
+
+ if (options.agent && options.agentOptions) {
+ console.warn(
+ "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent."
+ );
+ }
+
+ // Fetch root key for certificate validation during development
+ if (process.env.DFX_NETWORK !== "ic") {
+ agent.fetchRootKey().catch((err) => {
+ console.warn(
+ "Unable to fetch root key. Check to ensure that your local replica is running"
+ );
+ console.error(err);
+ });
+ }
+
+ // Creates an actor with using the candid interface and the HttpAgent
+ return Actor.createActor(idlFactory, {
+ agent,
+ canisterId,
+ ...options.actorOptions,
+ });
+};
diff --git a/apps/wallet/src/generated/station/station.did b/apps/wallet/src/generated/station/station.did
index a2fbdbd4f..ad4c9c2d4 100644
--- a/apps/wallet/src/generated/station/station.did
+++ b/apps/wallet/src/generated/station/station.did
@@ -59,6 +59,9 @@ type RequestSpecifier = variant {
EditUserGroup : ResourceIds;
RemoveUserGroup : ResourceIds;
ManageSystemInfo;
+ AddAsset;
+ EditAsset : ResourceIds;
+ RemoveAsset : ResourceIds;
};
// A record type that can be used to represent a percentage of users that are required to approve a rule.
@@ -322,6 +325,10 @@ type RequestApproval = record {
type TransferOperationInput = record {
// The account id to use for the transaction.
from_account_id : UUID;
+ // The asset id to transfer.
+ from_asset_id : UUID;
+ // The standard to use for the transfer.
+ with_standard : text;
// The amount to transfer.
amount : nat;
// The destination address of the transaction (e.g. "1BvBMSE...").
@@ -342,6 +349,8 @@ type TransferOperationInput = record {
type TransferOperation = record {
// The account to use for the transaction.
from_account : opt Account;
+ // The asset to use for the transaction.
+ from_asset : Asset;
// The network to use for the transaction.
network : Network;
// The input to the request to transfer funds.
@@ -352,12 +361,27 @@ type TransferOperation = record {
fee : opt nat;
};
+// Mutate the list of assets.
+type ChangeAssets = variant {
+ // Replace all current assets with the specified list.
+ ReplaceWith : record {
+ assets : vec UUID;
+ };
+ // Change the list of assets by adding and removing assets.
+ Change : record {
+ add_assets : vec UUID;
+ remove_assets : vec UUID;
+ };
+};
+
// Input type for editing an account through a request.
type EditAccountOperationInput = record {
// The account id that will be edited.
account_id : UUID;
// A friendly name for the account (e.g. "My Account").
name : opt text;
+ // Mutate the list of assets.
+ change_assets : opt ChangeAssets;
// Who can read the account information.
read_permission : opt Allow;
// Who can request configuration changes to the account.
@@ -379,10 +403,8 @@ type EditAccountOperation = record {
type AddAccountOperationInput = record {
// A friendly name for the account (e.g. "My Account").
name : text;
- // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
- blockchain : text;
- // The asset standard for this account (e.g. `native`, `erc20`, etc.).
- standard : text;
+ // The assets to add to the account.
+ assets : vec UUID;
// Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`).
metadata : vec AccountMetadata;
// Who can read the account information.
@@ -417,6 +439,8 @@ type AddAddressBookEntryOperationInput = record {
address_owner : text;
// The actual address.
address : text;
+ // The format of the address, eg. icp_account_identifier
+ address_format : text;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
blockchain : text;
// Metadata associated with the address book entry (e.g. `{"kyc": "true"}`).
@@ -922,6 +946,12 @@ type RequestOperation = variant {
RemoveRequestPolicy : RemoveRequestPolicyOperation;
// An operation for managing system info.
ManageSystemInfo : ManageSystemInfoOperation;
+ // An operation for adding a new asset.
+ AddAsset : AddAssetOperation;
+ // An operation for editing an existing asset.
+ EditAsset : EditAssetOperation;
+ // An operation for removing an existing asset.
+ RemoveAsset : RemoveAssetOperation;
};
type RequestOperationInput = variant {
@@ -971,6 +1001,12 @@ type RequestOperationInput = variant {
RemoveRequestPolicy : RemoveRequestPolicyOperationInput;
// An operation for managing system info.
ManageSystemInfo : ManageSystemInfoOperationInput;
+ // An operation for adding a new asset.
+ AddAsset : AddAssetOperationInput;
+ // An operation for editing an existing asset.
+ EditAsset : EditAssetOperationInput;
+ // An operation for removing an existing asset.
+ RemoveAsset : RemoveAssetOperationInput;
};
type RequestOperationType = variant {
@@ -1020,6 +1056,12 @@ type RequestOperationType = variant {
RemoveRequestPolicy;
// And operation for managing system info.
ManageSystemInfo;
+ // An operation for adding a new asset.
+ AddAsset;
+ // An operation for editing an existing asset.
+ EditAsset;
+ // An operation for removing an existing asset.
+ RemoveAsset;
};
// The schedule for executing a transaction of a given transfer.
@@ -1170,6 +1212,12 @@ type ListRequestsOperationType = variant {
ManageSystemInfo;
// An operation for setting disaster recovery config.
SetDisasterRecovery;
+ // An operation for adding an asset.
+ AddAsset;
+ // An operation for editing an asset.
+ EditAsset;
+ // An operation for removing an asset.
+ RemoveAsset;
};
// The direction to use for sorting.
@@ -1549,21 +1597,15 @@ type AccountCallerPrivileges = record {
type Account = record {
// The internal account id.
id : UUID;
- // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
- blockchain : text;
- // The asset symbol, e.g. "ICP" or "BTC".
- symbol : AssetSymbol;
- // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
- // with spaces replaced with underscores.
- standard : text;
- // The address of the account (e.g. "0x1234").
- address : text;
- // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
- decimals : nat32;
+
+ // The list of assets supported by this account.
+ assets : vec AccountAsset;
+
+ // The list of addresses associated with the account.
+ addresses : vec AccountAddress;
+
// A friendly name for the account.
name : text;
- // Account balance when available.
- balance : opt AccountBalanceInfo;
// Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`).
metadata : vec AccountMetadata;
// The transfer approval policy for the account.
@@ -1578,6 +1620,25 @@ type Account = record {
last_modification_timestamp : TimestampRFC3339;
};
+// The seed used to derive the addresses of the account.
+type AccountSeed = blob;
+
+// Record type to describe an address of an account.
+type AccountAddress = record {
+ // The address.
+ address : text;
+ // The format of the address, eg. icp_account_identifier.
+ format : text;
+};
+
+// Record type to describe an asset of an account.
+type AccountAsset = record {
+ // The asset id.
+ asset_id : UUID;
+ // The balance of the asset.
+ balance : opt AccountBalance;
+};
+
// Input type for getting a account.
type GetAccountInput = record {
// The account id to retrieve.
@@ -1600,12 +1661,19 @@ type GetAccountResult = variant {
type AccountBalance = record {
// The account id.
account_id : UUID;
+ // The asset id.
+ asset_id : UUID;
// The balance of the account.
balance : nat;
// The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
decimals : nat32;
// The time at which the balance was last updated.
last_update_timestamp : TimestampRFC3339;
+ // The state of balance query:
+ // - `fresh`: The balance was recently updated and is considered fresh.
+ // - `stale`: The balance may be out of date.
+ // - `stale_refreshing`: The balance may be out of date but it is being refreshed in the background.
+ query_state : text;
};
// Input type for getting a account balance.
@@ -1619,7 +1687,7 @@ type FetchAccountBalancesResult = variant {
// The result data for a successful execution.
Ok : record {
// The account balance that was retrieved.
- balances : vec AccountBalance;
+ balances : vec opt AccountBalance;
};
// The error that occurred (e.g. the user does not have the necessary permissions).
Err : Error;
@@ -1652,6 +1720,8 @@ type AddressBookEntry = record {
address_owner : text;
// The actual address.
address : text;
+ // The address format (e.g. "icp_account_identifier").
+ address_format : text;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
blockchain : text;
// Metadata associated with the address book entry (e.g. `{"kyc": "true"}`).
@@ -1691,6 +1761,8 @@ type ListAddressBookEntriesInput = record {
blockchain : opt text;
// The labels to search for, if provided only address book entries with the given labels will be returned.
labels : opt vec text;
+ // The address formats to search for.
+ address_formats : opt vec text;
// The pagination parameters.
paginate : opt PaginationInput;
};
@@ -1723,19 +1795,41 @@ type AssetMetadata = record {
// A record type that can be used to represent an asset in the station.
type Asset = record {
+ // The internal asset id.
+ id : UUID;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
blockchain : text;
// The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
// with spaces replaced with underscores.
- standard : text;
+ standards : vec text;
// The asset symbol, e.g. "ICP" or "BTC".
symbol : AssetSymbol;
// The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.)
name : text;
- // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`),
- // also, in the case of non-native assets, it can contain other required
- // information (e.g. `{"address": "0x1234"}`).
+ // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`).
metadata : vec AssetMetadata;
+ // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
+ decimals : nat32;
+};
+
+// Describes a standard suported by a blockchain.
+type StandardData = record {
+ // The standard name.
+ standard : text;
+ // Required metadata fields for the standard (e.g. `["ledger_canister_id"]`).
+ required_metadata_fields : vec text;
+ // Supported operations for the standard (e.g. `["transfer", "list_transfers", "balance"]`).
+ supported_operations : vec text;
+ // Supported address formats of the standard.
+ supported_address_formats : vec text;
+};
+
+// Describes a blockchain and its standards supported by the station.
+type SupportedBlockchain = record {
+ // The blockchain name.
+ blockchain : text;
+ // The supported standards for the blockchain.
+ supported_standards : vec StandardData;
};
// A record type that is used to show the current capabilities of the station.
@@ -1746,6 +1840,8 @@ type Capabilities = record {
version : text;
// The list of supported assets.
supported_assets : vec Asset;
+ // The list of supported blockchains and standards.
+ supported_blockchains : vec SupportedBlockchain;
};
// Result type for getting the current config.
@@ -2014,6 +2110,7 @@ type Resource = variant {
System : SystemResourceAction;
User : UserResourceAction;
UserGroup : ResourceAction;
+ Asset : ResourceAction;
};
// A record type that can be used to represent the caller privileges for a given permission.
@@ -2174,6 +2271,122 @@ type ListRequestPoliciesResult = variant {
Err : Error;
};
+type AddAssetOperation = record {
+ // The result of adding an asset.
+ asset : opt Asset;
+ // The input to the request to add an asset.
+ input : AddAssetOperationInput;
+};
+
+// The input type for adding an asset.
+type AddAssetOperationInput = record {
+ // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
+ blockchain : text;
+ // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
+ // with spaces replaced with underscores.
+ standards : vec text;
+ // The asset symbol, e.g. "ICP" or "BTC".
+ symbol : AssetSymbol;
+ // The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.)
+ name : text;
+ // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`).
+ metadata : vec AssetMetadata;
+ // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
+ decimals : nat32;
+};
+
+type EditAssetOperation = record {
+ // The input to the request to edit an asset.
+ input : EditAssetOperationInput;
+};
+
+// The input type for editing an asset.
+type EditAssetOperationInput = record {
+ // The asset id to edit.
+ asset_id : UUID;
+ // The name of the asset.
+ name : opt text;
+ // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
+ blockchain : opt text;
+ // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
+ // with spaces replaced with underscores.
+ standards : opt vec text;
+ // The asset symbol, e.g. "ICP" or "BTC".
+ symbol : opt AssetSymbol;
+ // The metadata to change.
+ change_metadata : opt ChangeMetadata;
+};
+
+// Type for instructions to update the address book entry's metadata.
+type ChangeMetadata = variant {
+ // Replace all existing metadata by the specified metadata.
+ ReplaceAllBy : vec AssetMetadata;
+ // Override values of existing metadata with the specified keys
+ // and add new metadata if no metadata can be found with the specified keys.
+ OverrideSpecifiedBy : vec AssetMetadata;
+ // Remove metadata with the specified keys.
+ RemoveKeys : vec text;
+};
+
+type RemoveAssetOperation = record {
+ // The input to the request to remove an asset.
+ input : RemoveAssetOperationInput;
+};
+
+// The input type for removing an asset.
+type RemoveAssetOperationInput = record {
+ // The asset id to remove.
+ asset_id : UUID;
+};
+
+// The input type for listing assets.
+type ListAssetsInput = record {
+ // The pagination parameters.
+ paginate : opt PaginationInput;
+};
+
+// The result type for listing assets.
+type ListAssetsResult = variant {
+ // The result data for a successful execution.
+ Ok : record {
+ // The list of assets.
+ assets : vec Asset;
+ // The offset to use for the next page.
+ next_offset : opt nat64;
+ // The total number of assets.
+ total : nat64;
+ // The caller privileges for the assets.
+ privileges : vec AssetCallerPrivileges;
+ };
+ // The error that occurred (e.g. the user does not have the necessary permissions).
+ Err : Error;
+};
+
+// The input type for getting an asset.
+type GetAssetInput = record {
+ // The asset id to retrieve.
+ asset_id : UUID;
+};
+
+// The result type for getting an asset.
+type GetAssetResult = variant {
+ // The result data for a successful execution.
+ Ok : record {
+ // The asset that was retrieved.
+ asset : Asset;
+ // The caller privileges for the asset.
+ privileges : AssetCallerPrivileges;
+ };
+ // The error that occurred (e.g. the user does not have the necessary permissions).
+ Err : Error;
+};
+
+type AssetCallerPrivileges = record {
+ id : UUID;
+ can_edit : bool;
+ can_delete : bool;
+};
+
// The top level privileges that the user has when making calls to the canister.
type UserPrivilege = variant {
Capabilities;
@@ -2195,6 +2408,8 @@ type UserPrivilege = variant {
CreateExternalCanister;
ListExternalCanisters;
CallAnyExternalCanister;
+ ListAssets;
+ AddAsset;
};
type MeResult = variant {
@@ -2230,13 +2445,31 @@ type InitAccountInput = record {
// A friendly name for the account (e.g. "My Account").
name : text;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
- blockchain : text;
+ seed : AccountSeed;
// The asset standard for this account (e.g. `native`, `erc20`, etc.).
- standard : text;
+ assets : vec UUID;
// Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`).
metadata : vec AccountMetadata;
};
+// The initial assets to create when initializing the canister for the first time, e.g., after disaster recovery.
+type InitAssetInput = record {
+ // The UUID of the asset, if not provided a new UUID will be generated.
+ id : UUID;
+ // The name of the asset.
+ name : text;
+ // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
+ blockchain : text;
+ // The standards this asset supports.
+ standards : vec text;
+ // The asset symbol, e.g. "ICP" or "BTC".
+ symbol : text;
+ // The number of decimals used to format the asset balance.
+ decimals : nat32;
+ // Metadata associated with the asset.
+ metadata : vec AssetMetadata;
+};
+
// The init configuration for the canister.
//
// Only used when installing the canister for the first time.
@@ -2253,6 +2486,8 @@ type SystemInit = record {
fallback_controller : opt principal;
// Optional initial accounts to create.
accounts : opt vec InitAccountInput;
+ // Optional initial assets to create.
+ assets : opt vec InitAssetInput;
};
// The upgrade configuration for the canister.
@@ -2766,4 +3001,8 @@ service : (opt SystemInstall) -> {
http_request : (HttpRequest) -> (HttpResponse) query;
// Internal endpoint used by the upgrader canister to notify the station about a failed station upgrade request.
notify_failed_station_upgrade : (NotifyFailedStationUpgradeInput) -> (NotifyFailedStationUpgradeResult);
+ // Get an asset by id.
+ get_asset : (input : GetAssetInput) -> (GetAssetResult) query;
+ // List all assets that the caller has access to.
+ list_assets : (input : ListAssetsInput) -> (ListAssetsResult) query;
};
diff --git a/apps/wallet/src/generated/station/station.did.d.ts b/apps/wallet/src/generated/station/station.did.d.ts
index 4655876e1..6d5d1b6f9 100644
--- a/apps/wallet/src/generated/station/station.did.d.ts
+++ b/apps/wallet/src/generated/station/station.did.d.ts
@@ -5,22 +5,25 @@ import type { IDL } from '@dfinity/candid';
export interface Account {
'id' : UUID,
'configs_request_policy' : [] | [RequestPolicyRule],
- 'decimals' : number,
- 'balance' : [] | [AccountBalanceInfo],
'metadata' : Array,
'name' : string,
- 'blockchain' : string,
- 'address' : string,
+ 'assets' : Array,
+ 'addresses' : Array,
'transfer_request_policy' : [] | [RequestPolicyRule],
'last_modification_timestamp' : TimestampRFC3339,
- 'standard' : string,
- 'symbol' : AssetSymbol,
+}
+export interface AccountAddress { 'address' : string, 'format' : string }
+export interface AccountAsset {
+ 'balance' : [] | [AccountBalance],
+ 'asset_id' : UUID,
}
export interface AccountBalance {
'account_id' : UUID,
'decimals' : number,
'balance' : bigint,
'last_update_timestamp' : TimestampRFC3339,
+ 'query_state' : string,
+ 'asset_id' : UUID,
}
export interface AccountBalanceInfo {
'decimals' : number,
@@ -38,6 +41,7 @@ export type AccountResourceAction = { 'List' : null } |
{ 'Create' : null } |
{ 'Transfer' : ResourceId } |
{ 'Update' : ResourceId };
+export type AccountSeed = Uint8Array | number[];
export interface AddAccountOperation {
'account' : [] | [Account],
'input' : AddAccountOperationInput,
@@ -48,10 +52,9 @@ export interface AddAccountOperationInput {
'configs_permission' : Allow,
'metadata' : Array,
'name' : string,
- 'blockchain' : string,
+ 'assets' : Array,
'transfer_request_policy' : [] | [RequestPolicyRule],
'transfer_permission' : Allow,
- 'standard' : string,
}
export interface AddAddressBookEntryOperation {
'address_book_entry' : [] | [AddressBookEntry],
@@ -62,8 +65,21 @@ export interface AddAddressBookEntryOperationInput {
'labels' : Array,
'blockchain' : string,
'address' : string,
+ 'address_format' : string,
'address_owner' : string,
}
+export interface AddAssetOperation {
+ 'asset' : [] | [Asset],
+ 'input' : AddAssetOperationInput,
+}
+export interface AddAssetOperationInput {
+ 'decimals' : number,
+ 'standards' : Array,
+ 'metadata' : Array,
+ 'name' : string,
+ 'blockchain' : string,
+ 'symbol' : AssetSymbol,
+}
export interface AddRequestPolicyOperation {
'input' : AddRequestPolicyOperationInput,
'policy_id' : [] | [UUID],
@@ -94,6 +110,7 @@ export interface AddressBookEntry {
'blockchain' : string,
'address' : string,
'last_modification_timestamp' : string,
+ 'address_format' : string,
'address_owner' : string,
}
export interface AddressBookEntryCallerPrivileges {
@@ -109,12 +126,19 @@ export interface Allow {
'users' : Array,
}
export interface Asset {
+ 'id' : UUID,
+ 'decimals' : number,
+ 'standards' : Array,
'metadata' : Array,
'name' : string,
'blockchain' : string,
- 'standard' : string,
'symbol' : AssetSymbol,
}
+export interface AssetCallerPrivileges {
+ 'id' : UUID,
+ 'can_delete' : boolean,
+ 'can_edit' : boolean,
+}
export interface AssetMetadata { 'key' : string, 'value' : string }
export type AssetSymbol = string;
export type AuthScope = { 'Authenticated' : null } |
@@ -185,6 +209,7 @@ export interface Capabilities {
'name' : string,
'version' : string,
'supported_assets' : Array,
+ 'supported_blockchains' : Array,
}
export type CapabilitiesResult = { 'Ok' : { 'capabilities' : Capabilities } } |
{ 'Err' : Error };
@@ -193,6 +218,8 @@ export type ChangeAddressBookMetadata = {
} |
{ 'RemoveKeys' : Array } |
{ 'ReplaceAllBy' : Array };
+export type ChangeAssets = { 'ReplaceWith' : { 'assets' : Array } } |
+ { 'Change' : { 'add_assets' : Array, 'remove_assets' : Array } };
export type ChangeExternalCanisterMetadata = {
'OverrideSpecifiedBy' : Array
} |
@@ -211,6 +238,9 @@ export interface ChangeExternalCanisterOperationInput {
'canister_id' : Principal,
'module' : Uint8Array | number[],
}
+export type ChangeMetadata = { 'OverrideSpecifiedBy' : Array } |
+ { 'RemoveKeys' : Array } |
+ { 'ReplaceAllBy' : Array };
export type ConfigureExternalCanisterOperation = ConfigureExternalCanisterOperationInput;
export interface ConfigureExternalCanisterOperationInput {
'kind' : ConfigureExternalCanisterOperationKind,
@@ -311,6 +341,7 @@ export interface EditAccountOperationInput {
'read_permission' : [] | [Allow],
'configs_permission' : [] | [Allow],
'name' : [] | [string],
+ 'change_assets' : [] | [ChangeAssets],
'transfer_request_policy' : [] | [RequestPolicyRuleInput],
'transfer_permission' : [] | [Allow],
}
@@ -323,6 +354,15 @@ export interface EditAddressBookEntryOperationInput {
'address_book_entry_id' : UUID,
'address_owner' : [] | [string],
}
+export interface EditAssetOperation { 'input' : EditAssetOperationInput }
+export interface EditAssetOperationInput {
+ 'standards' : [] | [Array],
+ 'name' : [] | [string],
+ 'blockchain' : [] | [string],
+ 'change_metadata' : [] | [ChangeMetadata],
+ 'asset_id' : UUID,
+ 'symbol' : [] | [AssetSymbol],
+}
export interface EditPermissionOperation {
'input' : EditPermissionOperationInput,
}
@@ -525,7 +565,7 @@ export type ExternalCanisterState = { 'Active' : null } |
{ 'Archived' : null };
export interface FetchAccountBalancesInput { 'account_ids' : Array }
export type FetchAccountBalancesResult = {
- 'Ok' : { 'balances' : Array }
+ 'Ok' : { 'balances' : Array<[] | [AccountBalance]> }
} |
{ 'Err' : Error };
export type FundExternalCanisterOperation = FundExternalCanisterOperationInput;
@@ -550,6 +590,11 @@ export type GetAddressBookEntryResult = {
}
} |
{ 'Err' : Error };
+export interface GetAssetInput { 'asset_id' : UUID }
+export type GetAssetResult = {
+ 'Ok' : { 'privileges' : AssetCallerPrivileges, 'asset' : Asset }
+ } |
+ { 'Err' : Error };
export interface GetExternalCanisterFiltersInput {
'with_labels' : [] | [boolean],
'with_name' : [] | [{ 'prefix' : [] | [string] }],
@@ -638,8 +683,17 @@ export interface InitAccountInput {
'id' : [] | [UUID],
'metadata' : Array,
'name' : string,
+ 'assets' : Array,
+ 'seed' : AccountSeed,
+}
+export interface InitAssetInput {
+ 'id' : UUID,
+ 'decimals' : number,
+ 'standards' : Array,
+ 'metadata' : Array,
+ 'name' : string,
'blockchain' : string,
- 'standard' : string,
+ 'symbol' : string,
}
export interface ListAccountTransfersInput {
'account_id' : UUID,
@@ -666,6 +720,7 @@ export type ListAccountsResult = {
{ 'Err' : Error };
export interface ListAddressBookEntriesInput {
'ids' : [] | [Array],
+ 'address_formats' : [] | [Array],
'labels' : [] | [Array],
'blockchain' : [] | [string],
'addresses' : [] | [Array],
@@ -680,6 +735,16 @@ export type ListAddressBookEntriesResult = {
}
} |
{ 'Err' : Error };
+export interface ListAssetsInput { 'paginate' : [] | [PaginationInput] }
+export type ListAssetsResult = {
+ 'Ok' : {
+ 'total' : bigint,
+ 'privileges' : Array,
+ 'assets' : Array,
+ 'next_offset' : [] | [bigint],
+ }
+ } |
+ { 'Err' : Error };
export interface ListExternalCanistersInput {
'sort_by' : [] | [ListExternalCanistersSortInput],
'states' : [] | [Array],
@@ -746,15 +811,18 @@ export interface ListRequestsInput {
'only_approvable' : boolean,
'created_from_dt' : [] | [TimestampRFC3339],
}
-export type ListRequestsOperationType = { 'AddUserGroup' : null } |
+export type ListRequestsOperationType = { 'RemoveAsset' : null } |
+ { 'AddUserGroup' : null } |
{ 'EditPermission' : null } |
{ 'ConfigureExternalCanister' : [] | [Principal] } |
{ 'ChangeExternalCanister' : [] | [Principal] } |
{ 'AddUser' : null } |
+ { 'EditAsset' : null } |
{ 'EditUserGroup' : null } |
{ 'SetDisasterRecovery' : null } |
{ 'EditRequestPolicy' : null } |
{ 'RemoveRequestPolicy' : null } |
+ { 'AddAsset' : null } |
{ 'SystemUpgrade' : null } |
{ 'RemoveAddressBookEntry' : null } |
{ 'CreateExternalCanister' : null } |
@@ -894,6 +962,8 @@ export interface RemoveAddressBookEntryOperation {
export interface RemoveAddressBookEntryOperationInput {
'address_book_entry_id' : UUID,
}
+export interface RemoveAssetOperation { 'input' : RemoveAssetOperationInput }
+export interface RemoveAssetOperationInput { 'asset_id' : UUID }
export interface RemoveRequestPolicyOperation {
'input' : RemoveRequestPolicyOperationInput,
}
@@ -940,15 +1010,18 @@ export interface RequestEvaluationResult {
}
export type RequestExecutionSchedule = { 'Immediate' : null } |
{ 'Scheduled' : { 'execution_time' : TimestampRFC3339 } };
-export type RequestOperation = { 'AddUserGroup' : AddUserGroupOperation } |
+export type RequestOperation = { 'RemoveAsset' : RemoveAssetOperation } |
+ { 'AddUserGroup' : AddUserGroupOperation } |
{ 'EditPermission' : EditPermissionOperation } |
{ 'ConfigureExternalCanister' : ConfigureExternalCanisterOperation } |
{ 'ChangeExternalCanister' : ChangeExternalCanisterOperation } |
{ 'AddUser' : AddUserOperation } |
+ { 'EditAsset' : EditAssetOperation } |
{ 'EditUserGroup' : EditUserGroupOperation } |
{ 'SetDisasterRecovery' : SetDisasterRecoveryOperation } |
{ 'EditRequestPolicy' : EditRequestPolicyOperation } |
{ 'RemoveRequestPolicy' : RemoveRequestPolicyOperation } |
+ { 'AddAsset' : AddAssetOperation } |
{ 'SystemUpgrade' : SystemUpgradeOperation } |
{ 'RemoveAddressBookEntry' : RemoveAddressBookEntryOperation } |
{ 'CreateExternalCanister' : CreateExternalCanisterOperation } |
@@ -964,16 +1037,19 @@ export type RequestOperation = { 'AddUserGroup' : AddUserGroupOperation } |
{ 'CallExternalCanister' : CallExternalCanisterOperation } |
{ 'AddAccount' : AddAccountOperation };
export type RequestOperationInput = {
- 'AddUserGroup' : AddUserGroupOperationInput
+ 'RemoveAsset' : RemoveAssetOperationInput
} |
+ { 'AddUserGroup' : AddUserGroupOperationInput } |
{ 'EditPermission' : EditPermissionOperationInput } |
{ 'ConfigureExternalCanister' : ConfigureExternalCanisterOperationInput } |
{ 'ChangeExternalCanister' : ChangeExternalCanisterOperationInput } |
{ 'AddUser' : AddUserOperationInput } |
+ { 'EditAsset' : EditAssetOperationInput } |
{ 'EditUserGroup' : EditUserGroupOperationInput } |
{ 'SetDisasterRecovery' : SetDisasterRecoveryOperationInput } |
{ 'EditRequestPolicy' : EditRequestPolicyOperationInput } |
{ 'RemoveRequestPolicy' : RemoveRequestPolicyOperationInput } |
+ { 'AddAsset' : AddAssetOperationInput } |
{ 'SystemUpgrade' : SystemUpgradeOperationInput } |
{ 'RemoveAddressBookEntry' : RemoveAddressBookEntryOperationInput } |
{ 'CreateExternalCanister' : CreateExternalCanisterOperationInput } |
@@ -988,15 +1064,18 @@ export type RequestOperationInput = {
{ 'RemoveUserGroup' : RemoveUserGroupOperationInput } |
{ 'CallExternalCanister' : CallExternalCanisterOperationInput } |
{ 'AddAccount' : AddAccountOperationInput };
-export type RequestOperationType = { 'AddUserGroup' : null } |
+export type RequestOperationType = { 'RemoveAsset' : null } |
+ { 'AddUserGroup' : null } |
{ 'EditPermission' : null } |
{ 'ConfigureExternalCanister' : null } |
{ 'ChangeExternalCanister' : null } |
{ 'AddUser' : null } |
+ { 'EditAsset' : null } |
{ 'EditUserGroup' : null } |
{ 'SetDisasterRecovery' : null } |
{ 'EditRequestPolicy' : null } |
{ 'RemoveRequestPolicy' : null } |
+ { 'AddAsset' : null } |
{ 'SystemUpgrade' : null } |
{ 'RemoveAddressBookEntry' : null } |
{ 'CreateExternalCanister' : null } |
@@ -1037,14 +1116,17 @@ export interface RequestPolicyRuleResult {
}
export type RequestResourceAction = { 'List' : null } |
{ 'Read' : ResourceId };
-export type RequestSpecifier = { 'AddUserGroup' : null } |
+export type RequestSpecifier = { 'RemoveAsset' : ResourceIds } |
+ { 'AddUserGroup' : null } |
{ 'EditPermission' : ResourceSpecifier } |
{ 'ChangeExternalCanister' : ExternalCanisterId } |
{ 'AddUser' : null } |
+ { 'EditAsset' : ResourceIds } |
{ 'EditUserGroup' : ResourceIds } |
{ 'SetDisasterRecovery' : null } |
{ 'EditRequestPolicy' : ResourceIds } |
{ 'RemoveRequestPolicy' : ResourceIds } |
+ { 'AddAsset' : null } |
{ 'SystemUpgrade' : null } |
{ 'RemoveAddressBookEntry' : ResourceIds } |
{ 'CreateExternalCanister' : null } |
@@ -1082,6 +1164,7 @@ export type Resource = { 'Request' : RequestResourceAction } |
{ 'ExternalCanister' : ExternalCanisterResourceAction } |
{ 'Account' : AccountResourceAction } |
{ 'AddressBook' : ResourceAction } |
+ { 'Asset' : ResourceAction } |
{ 'UserGroup' : ResourceAction } |
{ 'Permission' : PermissionResourceAction } |
{ 'RequestPolicy' : ResourceAction };
@@ -1105,6 +1188,12 @@ export interface SetDisasterRecoveryOperationInput {
export type Sha256Hash = string;
export type SortByDirection = { 'Asc' : null } |
{ 'Desc' : null };
+export interface StandardData {
+ 'supported_operations' : Array,
+ 'supported_address_formats' : Array,
+ 'required_metadata_fields' : Array,
+ 'standard' : string,
+}
export interface SubmitRequestApprovalInput {
'request_id' : UUID,
'decision' : RequestApprovalStatus,
@@ -1121,6 +1210,10 @@ export type SubmitRequestApprovalResult = {
export interface SubnetFilter { 'subnet_type' : [] | [string] }
export type SubnetSelection = { 'Filter' : SubnetFilter } |
{ 'Subnet' : { 'subnet' : Principal } };
+export interface SupportedBlockchain {
+ 'blockchain' : string,
+ 'supported_standards' : Array,
+}
export interface SystemInfo {
'disaster_recovery' : [] | [DisasterRecovery],
'name' : string,
@@ -1135,6 +1228,7 @@ export type SystemInfoResult = { 'Ok' : { 'system' : SystemInfo } } |
{ 'Err' : Error };
export interface SystemInit {
'name' : string,
+ 'assets' : [] | [Array],
'fallback_controller' : [] | [Principal],
'upgrader' : SystemUpgraderInput,
'accounts' : [] | [Array],
@@ -1186,6 +1280,7 @@ export interface TransferListItem {
export interface TransferMetadata { 'key' : string, 'value' : string }
export interface TransferOperation {
'fee' : [] | [bigint],
+ 'from_asset' : Asset,
'network' : Network,
'transfer_id' : [] | [UUID],
'from_account' : [] | [Account],
@@ -1194,10 +1289,12 @@ export interface TransferOperation {
export interface TransferOperationInput {
'to' : string,
'fee' : [] | [bigint],
+ 'with_standard' : string,
'from_account_id' : UUID,
'metadata' : Array,
'network' : [] | [Network],
'amount' : bigint,
+ 'from_asset_id' : UUID,
}
export type TransferStatus = { 'Failed' : { 'reason' : string } } |
{ 'Processing' : { 'started_at' : TimestampRFC3339 } } |
@@ -1235,8 +1332,10 @@ export type UserPrivilege = { 'AddUserGroup' : null } |
{ 'ListUserGroups' : null } |
{ 'AddUser' : null } |
{ 'ListUsers' : null } |
+ { 'AddAsset' : null } |
{ 'SystemUpgrade' : null } |
{ 'CreateExternalCanister' : null } |
+ { 'ListAssets' : null } |
{ 'ManageSystemInfo' : null } |
{ 'AddAddressBookEntry' : null } |
{ 'ListAccounts' : null } |
@@ -1278,6 +1377,7 @@ export interface _SERVICE {
[GetAddressBookEntryInput],
GetAddressBookEntryResult
>,
+ 'get_asset' : ActorMethod<[GetAssetInput], GetAssetResult>,
'get_external_canister' : ActorMethod<
[GetExternalCanisterInput],
GetExternalCanisterResult
@@ -1310,6 +1410,7 @@ export interface _SERVICE {
[ListAddressBookEntriesInput],
ListAddressBookEntriesResult
>,
+ 'list_assets' : ActorMethod<[ListAssetsInput], ListAssetsResult>,
'list_external_canisters' : ActorMethod<
[ListExternalCanistersInput],
ListExternalCanistersResult
diff --git a/apps/wallet/src/generated/station/station.did.js b/apps/wallet/src/generated/station/station.did.js
index 11a797576..be19c7b1f 100644
--- a/apps/wallet/src/generated/station/station.did.js
+++ b/apps/wallet/src/generated/station/station.did.js
@@ -2,18 +2,29 @@ export const idlFactory = ({ IDL }) => {
const RequestPolicyRule = IDL.Rec();
const RequestPolicyRuleResult = IDL.Rec();
const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) });
+ const UUID = IDL.Text;
+ const AssetMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text });
+ const InitAssetInput = IDL.Record({
+ 'id' : UUID,
+ 'decimals' : IDL.Nat32,
+ 'standards' : IDL.Vec(IDL.Text),
+ 'metadata' : IDL.Vec(AssetMetadata),
+ 'name' : IDL.Text,
+ 'blockchain' : IDL.Text,
+ 'symbol' : IDL.Text,
+ });
const SystemUpgraderInput = IDL.Variant({
'Id' : IDL.Principal,
'WasmModule' : IDL.Vec(IDL.Nat8),
});
- const UUID = IDL.Text;
const AccountMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text });
+ const AccountSeed = IDL.Vec(IDL.Nat8);
const InitAccountInput = IDL.Record({
'id' : IDL.Opt(UUID),
'metadata' : IDL.Vec(AccountMetadata),
'name' : IDL.Text,
- 'blockchain' : IDL.Text,
- 'standard' : IDL.Text,
+ 'assets' : IDL.Vec(UUID),
+ 'seed' : AccountSeed,
});
const AdminInitInput = IDL.Record({
'name' : IDL.Text,
@@ -21,6 +32,7 @@ export const idlFactory = ({ IDL }) => {
});
const SystemInit = IDL.Record({
'name' : IDL.Text,
+ 'assets' : IDL.Opt(IDL.Vec(InitAssetInput)),
'fallback_controller' : IDL.Opt(IDL.Principal),
'upgrader' : SystemUpgraderInput,
'accounts' : IDL.Opt(IDL.Vec(InitAccountInput)),
@@ -50,6 +62,10 @@ export const idlFactory = ({ IDL }) => {
'Immediate' : IDL.Null,
'Scheduled' : IDL.Record({ 'execution_time' : TimestampRFC3339 }),
});
+ const RemoveAssetOperationInput = IDL.Record({ 'asset_id' : UUID });
+ const RemoveAssetOperation = IDL.Record({
+ 'input' : RemoveAssetOperationInput,
+ });
const UserGroup = IDL.Record({ 'id' : UUID, 'name' : IDL.Text });
const AddUserGroupOperationInput = IDL.Record({ 'name' : IDL.Text });
const AddUserGroupOperation = IDL.Record({
@@ -131,6 +147,7 @@ export const idlFactory = ({ IDL }) => {
'ExternalCanister' : ExternalCanisterResourceAction,
'Account' : AccountResourceAction,
'AddressBook' : ResourceAction,
+ 'Asset' : ResourceAction,
'UserGroup' : ResourceAction,
'Permission' : PermissionResourceAction,
'RequestPolicy' : ResourceAction,
@@ -333,6 +350,21 @@ export const idlFactory = ({ IDL }) => {
'user' : IDL.Opt(User),
'input' : AddUserOperationInput,
});
+ const ChangeMetadata = IDL.Variant({
+ 'OverrideSpecifiedBy' : IDL.Vec(AssetMetadata),
+ 'RemoveKeys' : IDL.Vec(IDL.Text),
+ 'ReplaceAllBy' : IDL.Vec(AssetMetadata),
+ });
+ const AssetSymbol = IDL.Text;
+ const EditAssetOperationInput = IDL.Record({
+ 'standards' : IDL.Opt(IDL.Vec(IDL.Text)),
+ 'name' : IDL.Opt(IDL.Text),
+ 'blockchain' : IDL.Opt(IDL.Text),
+ 'change_metadata' : IDL.Opt(ChangeMetadata),
+ 'asset_id' : UUID,
+ 'symbol' : IDL.Opt(AssetSymbol),
+ });
+ const EditAssetOperation = IDL.Record({ 'input' : EditAssetOperationInput });
const EditUserGroupOperationInput = IDL.Record({
'name' : IDL.Text,
'user_group_id' : UUID,
@@ -347,20 +379,23 @@ export const idlFactory = ({ IDL }) => {
const SetDisasterRecoveryOperation = IDL.Record({
'committee' : IDL.Opt(DisasterRecoveryCommittee),
});
+ const ResourceIds = IDL.Variant({ 'Any' : IDL.Null, 'Ids' : IDL.Vec(UUID) });
const ResourceSpecifier = IDL.Variant({
'Any' : IDL.Null,
'Resource' : Resource,
});
- const ResourceIds = IDL.Variant({ 'Any' : IDL.Null, 'Ids' : IDL.Vec(UUID) });
const RequestSpecifier = IDL.Variant({
+ 'RemoveAsset' : ResourceIds,
'AddUserGroup' : IDL.Null,
'EditPermission' : ResourceSpecifier,
'ChangeExternalCanister' : ExternalCanisterId,
'AddUser' : IDL.Null,
+ 'EditAsset' : ResourceIds,
'EditUserGroup' : ResourceIds,
'SetDisasterRecovery' : IDL.Null,
'EditRequestPolicy' : ResourceIds,
'RemoveRequestPolicy' : ResourceIds,
+ 'AddAsset' : IDL.Null,
'SystemUpgrade' : IDL.Null,
'RemoveAddressBookEntry' : ResourceIds,
'CreateExternalCanister' : IDL.Null,
@@ -388,6 +423,27 @@ export const idlFactory = ({ IDL }) => {
const RemoveRequestPolicyOperation = IDL.Record({
'input' : RemoveRequestPolicyOperationInput,
});
+ const Asset = IDL.Record({
+ 'id' : UUID,
+ 'decimals' : IDL.Nat32,
+ 'standards' : IDL.Vec(IDL.Text),
+ 'metadata' : IDL.Vec(AssetMetadata),
+ 'name' : IDL.Text,
+ 'blockchain' : IDL.Text,
+ 'symbol' : AssetSymbol,
+ });
+ const AddAssetOperationInput = IDL.Record({
+ 'decimals' : IDL.Nat32,
+ 'standards' : IDL.Vec(IDL.Text),
+ 'metadata' : IDL.Vec(AssetMetadata),
+ 'name' : IDL.Text,
+ 'blockchain' : IDL.Text,
+ 'symbol' : AssetSymbol,
+ });
+ const AddAssetOperation = IDL.Record({
+ 'asset' : IDL.Opt(Asset),
+ 'input' : AddAssetOperationInput,
+ });
const SystemUpgradeTarget = IDL.Variant({
'UpgradeUpgrader' : IDL.Null,
'UpgradeStation' : IDL.Null,
@@ -489,37 +545,46 @@ export const idlFactory = ({ IDL }) => {
});
const NetworkId = IDL.Text;
const Network = IDL.Record({ 'id' : NetworkId, 'name' : IDL.Text });
- const AccountBalanceInfo = IDL.Record({
+ const AccountBalance = IDL.Record({
+ 'account_id' : UUID,
'decimals' : IDL.Nat32,
'balance' : IDL.Nat,
'last_update_timestamp' : TimestampRFC3339,
+ 'query_state' : IDL.Text,
+ 'asset_id' : UUID,
+ });
+ const AccountAsset = IDL.Record({
+ 'balance' : IDL.Opt(AccountBalance),
+ 'asset_id' : UUID,
+ });
+ const AccountAddress = IDL.Record({
+ 'address' : IDL.Text,
+ 'format' : IDL.Text,
});
- const AssetSymbol = IDL.Text;
const Account = IDL.Record({
'id' : UUID,
'configs_request_policy' : IDL.Opt(RequestPolicyRule),
- 'decimals' : IDL.Nat32,
- 'balance' : IDL.Opt(AccountBalanceInfo),
'metadata' : IDL.Vec(AccountMetadata),
'name' : IDL.Text,
- 'blockchain' : IDL.Text,
- 'address' : IDL.Text,
+ 'assets' : IDL.Vec(AccountAsset),
+ 'addresses' : IDL.Vec(AccountAddress),
'transfer_request_policy' : IDL.Opt(RequestPolicyRule),
'last_modification_timestamp' : TimestampRFC3339,
- 'standard' : IDL.Text,
- 'symbol' : AssetSymbol,
});
const TransferMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text });
const TransferOperationInput = IDL.Record({
'to' : IDL.Text,
'fee' : IDL.Opt(IDL.Nat),
+ 'with_standard' : IDL.Text,
'from_account_id' : UUID,
'metadata' : IDL.Vec(TransferMetadata),
'network' : IDL.Opt(Network),
'amount' : IDL.Nat,
+ 'from_asset_id' : UUID,
});
const TransferOperation = IDL.Record({
'fee' : IDL.Opt(IDL.Nat),
+ 'from_asset' : Asset,
'network' : Network,
'transfer_id' : IDL.Opt(UUID),
'from_account' : IDL.Opt(Account),
@@ -529,12 +594,20 @@ export const idlFactory = ({ IDL }) => {
'Set' : RequestPolicyRule,
'Remove' : IDL.Null,
});
+ const ChangeAssets = IDL.Variant({
+ 'ReplaceWith' : IDL.Record({ 'assets' : IDL.Vec(UUID) }),
+ 'Change' : IDL.Record({
+ 'add_assets' : IDL.Vec(UUID),
+ 'remove_assets' : IDL.Vec(UUID),
+ }),
+ });
const EditAccountOperationInput = IDL.Record({
'account_id' : UUID,
'configs_request_policy' : IDL.Opt(RequestPolicyRuleInput),
'read_permission' : IDL.Opt(Allow),
'configs_permission' : IDL.Opt(Allow),
'name' : IDL.Opt(IDL.Text),
+ 'change_assets' : IDL.Opt(ChangeAssets),
'transfer_request_policy' : IDL.Opt(RequestPolicyRuleInput),
'transfer_permission' : IDL.Opt(Allow),
});
@@ -548,6 +621,7 @@ export const idlFactory = ({ IDL }) => {
'blockchain' : IDL.Text,
'address' : IDL.Text,
'last_modification_timestamp' : IDL.Text,
+ 'address_format' : IDL.Text,
'address_owner' : IDL.Text,
});
const AddAddressBookEntryOperationInput = IDL.Record({
@@ -555,6 +629,7 @@ export const idlFactory = ({ IDL }) => {
'labels' : IDL.Vec(IDL.Text),
'blockchain' : IDL.Text,
'address' : IDL.Text,
+ 'address_format' : IDL.Text,
'address_owner' : IDL.Text,
});
const AddAddressBookEntryOperation = IDL.Record({
@@ -588,25 +663,27 @@ export const idlFactory = ({ IDL }) => {
'configs_permission' : Allow,
'metadata' : IDL.Vec(AccountMetadata),
'name' : IDL.Text,
- 'blockchain' : IDL.Text,
+ 'assets' : IDL.Vec(UUID),
'transfer_request_policy' : IDL.Opt(RequestPolicyRule),
'transfer_permission' : Allow,
- 'standard' : IDL.Text,
});
const AddAccountOperation = IDL.Record({
'account' : IDL.Opt(Account),
'input' : AddAccountOperationInput,
});
const RequestOperation = IDL.Variant({
+ 'RemoveAsset' : RemoveAssetOperation,
'AddUserGroup' : AddUserGroupOperation,
'EditPermission' : EditPermissionOperation,
'ConfigureExternalCanister' : ConfigureExternalCanisterOperation,
'ChangeExternalCanister' : ChangeExternalCanisterOperation,
'AddUser' : AddUserOperation,
+ 'EditAsset' : EditAssetOperation,
'EditUserGroup' : EditUserGroupOperation,
'SetDisasterRecovery' : SetDisasterRecoveryOperation,
'EditRequestPolicy' : EditRequestPolicyOperation,
'RemoveRequestPolicy' : RemoveRequestPolicyOperation,
+ 'AddAsset' : AddAssetOperation,
'SystemUpgrade' : SystemUpgradeOperation,
'RemoveAddressBookEntry' : RemoveAddressBookEntryOperation,
'CreateExternalCanister' : CreateExternalCanisterOperation,
@@ -686,18 +763,21 @@ export const idlFactory = ({ IDL }) => {
'Ok' : CanisterStatusResponse,
'Err' : Error,
});
- const AssetMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text });
- const Asset = IDL.Record({
- 'metadata' : IDL.Vec(AssetMetadata),
- 'name' : IDL.Text,
- 'blockchain' : IDL.Text,
+ const StandardData = IDL.Record({
+ 'supported_operations' : IDL.Vec(IDL.Text),
+ 'supported_address_formats' : IDL.Vec(IDL.Text),
+ 'required_metadata_fields' : IDL.Vec(IDL.Text),
'standard' : IDL.Text,
- 'symbol' : AssetSymbol,
+ });
+ const SupportedBlockchain = IDL.Record({
+ 'blockchain' : IDL.Text,
+ 'supported_standards' : IDL.Vec(StandardData),
});
const Capabilities = IDL.Record({
'name' : IDL.Text,
'version' : IDL.Text,
'supported_assets' : IDL.Vec(Asset),
+ 'supported_blockchains' : IDL.Vec(SupportedBlockchain),
});
const CapabilitiesResult = IDL.Variant({
'Ok' : IDL.Record({ 'capabilities' : Capabilities }),
@@ -731,15 +811,18 @@ export const idlFactory = ({ IDL }) => {
'execution_method_cycles' : IDL.Opt(IDL.Nat64),
});
const RequestOperationInput = IDL.Variant({
+ 'RemoveAsset' : RemoveAssetOperationInput,
'AddUserGroup' : AddUserGroupOperationInput,
'EditPermission' : EditPermissionOperationInput,
'ConfigureExternalCanister' : ConfigureExternalCanisterOperationInput,
'ChangeExternalCanister' : ChangeExternalCanisterOperationInput,
'AddUser' : AddUserOperationInput,
+ 'EditAsset' : EditAssetOperationInput,
'EditUserGroup' : EditUserGroupOperationInput,
'SetDisasterRecovery' : SetDisasterRecoveryOperationInput,
'EditRequestPolicy' : EditRequestPolicyOperationInput,
'RemoveRequestPolicy' : RemoveRequestPolicyOperationInput,
+ 'AddAsset' : AddAssetOperationInput,
'SystemUpgrade' : SystemUpgradeOperationInput,
'RemoveAddressBookEntry' : RemoveAddressBookEntryOperationInput,
'CreateExternalCanister' : CreateExternalCanisterOperationInput,
@@ -825,14 +908,8 @@ export const idlFactory = ({ IDL }) => {
const FetchAccountBalancesInput = IDL.Record({
'account_ids' : IDL.Vec(UUID),
});
- const AccountBalance = IDL.Record({
- 'account_id' : UUID,
- 'decimals' : IDL.Nat32,
- 'balance' : IDL.Nat,
- 'last_update_timestamp' : TimestampRFC3339,
- });
const FetchAccountBalancesResult = IDL.Variant({
- 'Ok' : IDL.Record({ 'balances' : IDL.Vec(AccountBalance) }),
+ 'Ok' : IDL.Record({ 'balances' : IDL.Vec(IDL.Opt(AccountBalance)) }),
'Err' : Error,
});
const GetAccountInput = IDL.Record({ 'account_id' : UUID });
@@ -863,6 +940,19 @@ export const idlFactory = ({ IDL }) => {
}),
'Err' : Error,
});
+ const GetAssetInput = IDL.Record({ 'asset_id' : UUID });
+ const AssetCallerPrivileges = IDL.Record({
+ 'id' : UUID,
+ 'can_delete' : IDL.Bool,
+ 'can_edit' : IDL.Bool,
+ });
+ const GetAssetResult = IDL.Variant({
+ 'Ok' : IDL.Record({
+ 'privileges' : AssetCallerPrivileges,
+ 'asset' : Asset,
+ }),
+ 'Err' : Error,
+ });
const GetExternalCanisterInput = IDL.Record({
'canister_id' : IDL.Principal,
});
@@ -927,15 +1017,18 @@ export const idlFactory = ({ IDL }) => {
'Err' : Error,
});
const ListRequestsOperationType = IDL.Variant({
+ 'RemoveAsset' : IDL.Null,
'AddUserGroup' : IDL.Null,
'EditPermission' : IDL.Null,
'ConfigureExternalCanister' : IDL.Opt(IDL.Principal),
'ChangeExternalCanister' : IDL.Opt(IDL.Principal),
'AddUser' : IDL.Null,
+ 'EditAsset' : IDL.Null,
'EditUserGroup' : IDL.Null,
'SetDisasterRecovery' : IDL.Null,
'EditRequestPolicy' : IDL.Null,
'RemoveRequestPolicy' : IDL.Null,
+ 'AddAsset' : IDL.Null,
'SystemUpgrade' : IDL.Null,
'RemoveAddressBookEntry' : IDL.Null,
'CreateExternalCanister' : IDL.Null,
@@ -1110,6 +1203,7 @@ export const idlFactory = ({ IDL }) => {
});
const ListAddressBookEntriesInput = IDL.Record({
'ids' : IDL.Opt(IDL.Vec(UUID)),
+ 'address_formats' : IDL.Opt(IDL.Vec(IDL.Text)),
'labels' : IDL.Opt(IDL.Vec(IDL.Text)),
'blockchain' : IDL.Opt(IDL.Text),
'addresses' : IDL.Opt(IDL.Vec(IDL.Text)),
@@ -1124,6 +1218,16 @@ export const idlFactory = ({ IDL }) => {
}),
'Err' : Error,
});
+ const ListAssetsInput = IDL.Record({ 'paginate' : IDL.Opt(PaginationInput) });
+ const ListAssetsResult = IDL.Variant({
+ 'Ok' : IDL.Record({
+ 'total' : IDL.Nat64,
+ 'privileges' : IDL.Vec(AssetCallerPrivileges),
+ 'assets' : IDL.Vec(Asset),
+ 'next_offset' : IDL.Opt(IDL.Nat64),
+ }),
+ 'Err' : Error,
+ });
const SortByDirection = IDL.Variant({ 'Asc' : IDL.Null, 'Desc' : IDL.Null });
const ListExternalCanistersSortInput = IDL.Variant({
'Name' : SortByDirection,
@@ -1159,15 +1263,18 @@ export const idlFactory = ({ IDL }) => {
'notification_type' : IDL.Opt(NotificationTypeInput),
});
const RequestOperationType = IDL.Variant({
+ 'RemoveAsset' : IDL.Null,
'AddUserGroup' : IDL.Null,
'EditPermission' : IDL.Null,
'ConfigureExternalCanister' : IDL.Null,
'ChangeExternalCanister' : IDL.Null,
'AddUser' : IDL.Null,
+ 'EditAsset' : IDL.Null,
'EditUserGroup' : IDL.Null,
'SetDisasterRecovery' : IDL.Null,
'EditRequestPolicy' : IDL.Null,
'RemoveRequestPolicy' : IDL.Null,
+ 'AddAsset' : IDL.Null,
'SystemUpgrade' : IDL.Null,
'RemoveAddressBookEntry' : IDL.Null,
'CreateExternalCanister' : IDL.Null,
@@ -1327,8 +1434,10 @@ export const idlFactory = ({ IDL }) => {
'ListUserGroups' : IDL.Null,
'AddUser' : IDL.Null,
'ListUsers' : IDL.Null,
+ 'AddAsset' : IDL.Null,
'SystemUpgrade' : IDL.Null,
'CreateExternalCanister' : IDL.Null,
+ 'ListAssets' : IDL.Null,
'ManageSystemInfo' : IDL.Null,
'AddAddressBookEntry' : IDL.Null,
'ListAccounts' : IDL.Null,
@@ -1416,6 +1525,7 @@ export const idlFactory = ({ IDL }) => {
[GetAddressBookEntryResult],
['query'],
),
+ 'get_asset' : IDL.Func([GetAssetInput], [GetAssetResult], ['query']),
'get_external_canister' : IDL.Func(
[GetExternalCanisterInput],
[GetExternalCanisterResult],
@@ -1470,6 +1580,7 @@ export const idlFactory = ({ IDL }) => {
[ListAddressBookEntriesResult],
['query'],
),
+ 'list_assets' : IDL.Func([ListAssetsInput], [ListAssetsResult], ['query']),
'list_external_canisters' : IDL.Func(
[ListExternalCanistersInput],
[ListExternalCanistersResult],
@@ -1522,18 +1633,29 @@ export const idlFactory = ({ IDL }) => {
};
export const init = ({ IDL }) => {
const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) });
+ const UUID = IDL.Text;
+ const AssetMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text });
+ const InitAssetInput = IDL.Record({
+ 'id' : UUID,
+ 'decimals' : IDL.Nat32,
+ 'standards' : IDL.Vec(IDL.Text),
+ 'metadata' : IDL.Vec(AssetMetadata),
+ 'name' : IDL.Text,
+ 'blockchain' : IDL.Text,
+ 'symbol' : IDL.Text,
+ });
const SystemUpgraderInput = IDL.Variant({
'Id' : IDL.Principal,
'WasmModule' : IDL.Vec(IDL.Nat8),
});
- const UUID = IDL.Text;
const AccountMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text });
+ const AccountSeed = IDL.Vec(IDL.Nat8);
const InitAccountInput = IDL.Record({
'id' : IDL.Opt(UUID),
'metadata' : IDL.Vec(AccountMetadata),
'name' : IDL.Text,
- 'blockchain' : IDL.Text,
- 'standard' : IDL.Text,
+ 'assets' : IDL.Vec(UUID),
+ 'seed' : AccountSeed,
});
const AdminInitInput = IDL.Record({
'name' : IDL.Text,
@@ -1541,6 +1663,7 @@ export const init = ({ IDL }) => {
});
const SystemInit = IDL.Record({
'name' : IDL.Text,
+ 'assets' : IDL.Opt(IDL.Vec(InitAssetInput)),
'fallback_controller' : IDL.Opt(IDL.Principal),
'upgrader' : SystemUpgraderInput,
'accounts' : IDL.Opt(IDL.Vec(InitAccountInput)),
diff --git a/apps/wallet/src/locales/en.locale.ts b/apps/wallet/src/locales/en.locale.ts
index 694db6e60..297cd51c1 100644
--- a/apps/wallet/src/locales/en.locale.ts
+++ b/apps/wallet/src/locales/en.locale.ts
@@ -95,6 +95,7 @@ export default {
verify_instructions:
'To verify the update, open the terminal and follow the instructions bellow:',
},
+ asset: 'Asset',
no_data: 'No data available.',
no_matching_results: 'No matching results found for `{search}`.',
add_new_label: 'Add new label: {label}',
@@ -110,7 +111,12 @@ export default {
icp: {
name: 'Internet Computer',
standards: {
- native: 'Native',
+ icp_native: 'ICP (Native)',
+ icrc1: 'ICRC-1',
+ },
+ formats: {
+ icp_account_identifier: 'ICP Native',
+ icrc1_account: 'ICRC-1',
},
},
eth: {
@@ -169,6 +175,7 @@ export default {
system: 'System',
transfers: 'Transfers',
users: 'Users',
+ assets: 'Assets',
external_canisters: 'Canisters',
},
headers: {
@@ -265,6 +272,9 @@ export default {
editaccount: {
title: 'Edit account',
request_title: 'Edit account request',
+ added_assets: 'Added',
+ removed_assets: 'Removed',
+ replaced_assets: 'Replaced',
},
editaddressbookentry: {
title: 'Edit address book entry',
@@ -282,6 +292,18 @@ export default {
title: 'Manage system info',
request_title: 'Manage system info request',
},
+ addasset: {
+ title: 'Add asset',
+ request_title: 'Add asset request',
+ },
+ editasset: {
+ title: 'Edit asset',
+ request_title: 'Edit asset request',
+ },
+ removeasset: {
+ title: 'Remove asset',
+ request_title: 'Remove asset request',
+ },
createexternalcanister: {
title: 'Create canister',
request_title: 'Create canister request',
@@ -696,6 +718,7 @@ export default {
principal: 'Principal',
status: 'Status',
transfer: 'Transfer',
+ transfer_asset: 'Transfer {asset}',
invalid: 'Invalid',
control_panel: 'Control panel',
confirmed: 'Confirmed',
@@ -710,6 +733,9 @@ export default {
version: 'Version',
continue: 'Continue',
cycle_obtain_strategy: 'Wallet top-up method',
+ symbol: 'Symbol',
+ standards: 'Standards',
+ assets: 'Assets',
},
forms: {
create: 'Create',
@@ -732,11 +758,15 @@ export default {
numberRange: 'This field must be between {min} and {max}.',
invalidDecimalPlaces: 'This field must have a maximum of {decimals} decimal places.',
isHex: 'This field must be a valid hexadecimal value.',
+ validAddress: 'This field must be a valid address.',
+ validSymbol: 'Symbol must be 1-32 alphanumeric characters.',
},
},
navigation: {
home: 'Home',
+ dashboard: 'Dashboard',
accounts: 'Accounts',
+ account: 'Account',
address_book: 'Address Book',
users: 'Users',
settings: 'Settings',
@@ -750,9 +780,14 @@ export default {
transfer_requests: 'Transfer Requests',
permissions: 'Permissions',
request_policies: 'Request Policies',
+ assets: 'Assets',
external_canisters: 'Canisters',
},
pages: {
+ dashboard: {
+ title: 'Dashboard',
+ available_assets: 'Available Assets',
+ },
accounts: {
title: 'Accounts',
btn_new_transfer: 'New transfer',
@@ -773,6 +808,14 @@ export default {
csv_ignored_transfers_hint: 'Transfers with errors will be ignored.',
csv_transfer_failed: 'Failed to process transfers, please try again.',
csv_download_invalid: 'Download invalid',
+ add_asset: 'Add asset',
+ remove_asset: 'Remove asset',
+ no_assets_to_add: 'No assets available to add.',
+ remove_asset_confirm:
+ 'Are you sure you want to remove this asset? Removing the asset does not affect the account balance. Re-adding the asset will restore access to the balance.',
+ transfers_not_supported: 'Transfers are not supported for this asset.',
+ add_index_canister_to_see_transactions:
+ 'Consider adding the index canister to the asset to see transactions.',
},
address_book: {
title: 'Address Book',
@@ -871,6 +914,17 @@ export default {
create_label: 'Add Policy',
dialog_title: 'Policy',
},
+ assets: {
+ title: 'Assets',
+ btn_new_entry: 'New asset',
+ no_results_found: 'No assets found.',
+ error_fetching_assets: 'Error fetching assets, please try again.',
+ forms: {
+ ledger_canister_id: 'Ledger Canister ID',
+ index_canister_id: 'Index Canister ID',
+ decimals: 'Decimals',
+ },
+ },
not_found: {
title: 'Whoops, 404',
subtitle: 'The page you were looking for does not exist.',
@@ -910,6 +964,7 @@ export default {
select_resource: 'Resource Type',
resources: {
account: 'Account',
+ asset: 'Asset',
user: 'User',
usergroup: 'User Group',
permission: 'Access Policy',
@@ -988,6 +1043,9 @@ export default {
setdisasterrecovery: 'Configure disaster recovery',
callexternalcanister: 'Call canister',
createexternalcanister: 'Create canister',
+ addasset: 'Add asset',
+ editasset: 'Edit asset',
+ removeasset: 'Remove asset',
},
},
cycle_obtain_strategies: {
diff --git a/apps/wallet/src/locales/fr.locale.ts b/apps/wallet/src/locales/fr.locale.ts
index 4dc027894..7a66c2709 100644
--- a/apps/wallet/src/locales/fr.locale.ts
+++ b/apps/wallet/src/locales/fr.locale.ts
@@ -97,6 +97,7 @@ export default {
verify_instructions:
'Pour vérifier la mise à jour, ouvrez le terminal et suivez les instructions ci-dessous:',
},
+ asset: 'Actif',
no_data: 'Pas de données disponibles.',
no_matching_results: 'Pas de résultats correspondants trouvés pour `{search}`.',
add_new_label: 'Ajouter une nouvelle étiquette: {label}',
@@ -120,7 +121,12 @@ export default {
icp: {
name: 'Internet Computer',
standards: {
- native: 'Native',
+ icp_native: 'ICP (Native)',
+ icrc1: 'ICRC-1',
+ },
+ formats: {
+ icp_account_identifier: 'ICP Native',
+ icrc1_account: 'ICRC-1',
},
},
eth: {
@@ -179,6 +185,7 @@ export default {
system: 'Système',
transfers: 'Transferts',
users: 'Usagers',
+ assets: 'Actifs',
external_canisters: 'Canisters',
},
headers: {
@@ -275,6 +282,9 @@ export default {
editaccount: {
title: 'Modifier de modifier un compte',
request_title: 'Demande de modifier un compte',
+ added_assets: 'Ajouté',
+ removed_assets: 'Supprimé',
+ replaced_assets: 'Remplacé',
},
editaddressbookentry: {
title: "Modifier une entrée de carnet d'adresses",
@@ -292,6 +302,18 @@ export default {
title: 'Gérer les informations système',
request_title: 'Demande de gérer les informations système',
},
+ addasset: {
+ title: 'Ajouter un actif',
+ request_title: 'Demande d ajouter un actif',
+ },
+ editasset: {
+ title: 'Modifier un actif',
+ request_title: 'Demande de modifier un actif',
+ },
+ removeasset: {
+ title: 'Supprimer un actif',
+ request_title: 'Demande de supprimer un actif',
+ },
createexternalcanister: {
title: 'Créer un canister',
request_title: 'Demande de création de canister',
@@ -704,6 +726,7 @@ export default {
principal: 'Principal',
status: 'Statut',
transfer: 'Transfert',
+ transfer_asset: 'Transfert {asset}',
invalid: 'Invalide',
control_panel: 'Paneau de Contrôle',
confirmed: 'Confirmé',
@@ -718,6 +741,9 @@ export default {
version: 'Version',
continue: 'Continuer',
cycle_obtain_strategy: 'Méthode de recharge du portefeuille',
+ symbol: 'Symbole',
+ standards: 'Standards',
+ assets: 'Actifs',
},
forms: {
create: 'Créer',
@@ -740,11 +766,15 @@ export default {
numberRange: 'Le champ doit contenir une valeur valide entre {min} et {max}.',
invalidDecimalPlaces: 'Ce champ doit contenir un maximum de {decimals} décimales.',
isHex: 'Ce champ doit contenir une valeur hexadécimale valide.',
+ validAddress: 'Ce champ doit contenir une adresse valide.',
+ validSymbol: 'Le symbole doit contenir entre 1 et 32 charactères alphanumériques.',
},
},
navigation: {
home: 'Acceuil',
+ dashboard: 'Tableau de Bord',
accounts: 'Comptes',
+ account: 'Compte',
address_book: "Carnet d'Adresses",
users: 'Usagers',
settings: 'Settings',
@@ -758,9 +788,14 @@ export default {
transfer_requests: 'Demandes de Transfert',
permissions: "Polices d'Accés",
request_policies: "Polices d'Aprobation",
+ assets: 'Actifs',
external_canisters: 'Canisters',
},
pages: {
+ dashboard: {
+ title: 'Tableau de Bord',
+ available_assets: 'Actifs Disponibles',
+ },
accounts: {
title: 'Comptes',
btn_new_transfer: 'Nouveau Transfert',
@@ -783,6 +818,14 @@ export default {
csv_ignored_transfers_hint: 'Transfers with errors will be ignored.',
csv_transfer_failed: 'Échec de process transfers, veuillez essayer de nouveau.',
csv_download_invalid: 'Téléchargement invalide',
+ add_asset: 'Ajouter un actif',
+ remove_asset: 'Supprimer un actif',
+ no_assets_to_add: 'Pas d actifs disponibles à ajouter.',
+ remove_asset_confirm:
+ 'Êtes-vous sûr de vouloir supprimer cet actif? Supprimer l actif n affecte pas le solde du compte. Réajouter l actif restaurera l accès au solde.',
+ transfers_not_supported: 'Les transferts ne sont pas supportés pour cet actif.',
+ add_index_canister_to_see_transactions:
+ 'Considérez d ajouter le canister d index à l actif pour voir les transactions.',
},
address_book: {
title: "Carnet d'Adresses",
@@ -884,6 +927,17 @@ export default {
create_label: 'Ajouter un police',
dialog_title: 'Police',
},
+ assets: {
+ title: 'Actifs',
+ btn_new_asset: 'Nouvel Actif',
+ no_results_found: 'Pas d actif trouvé.',
+ error_fetching_assets: 'Erreur lors du chargement des actifs, veuillez essayer de nouveau.',
+ forms: {
+ ledger_canister_id: 'ID du Canister Ledger',
+ index_canister_id: 'ID du Canister Index',
+ decimals: 'Décimales',
+ },
+ },
not_found: {
title: 'Oulala, 404',
subtitle: "La page que vous cherchez n'existe pas.",
@@ -923,6 +977,7 @@ export default {
select_resource: 'Type de Resource',
resources: {
account: 'Compte',
+ asset: 'Actif',
user: 'Usager',
usergroup: "Groupe d'Usagers",
permission: "Police d'Accés",
@@ -1001,6 +1056,9 @@ export default {
setdisasterrecovery: 'Définir la récupération après sinistre',
callexternalcanister: 'Appeler un canister',
createexternalcanister: 'Créer un canister',
+ addasset: 'Ajouter un actif',
+ editasset: 'Modifier un actif',
+ removeasset: 'Éffacer un actif',
},
},
cycle_obtain_strategies: {
diff --git a/apps/wallet/src/locales/pt.locale.ts b/apps/wallet/src/locales/pt.locale.ts
index 65fd5ce69..5275309ef 100644
--- a/apps/wallet/src/locales/pt.locale.ts
+++ b/apps/wallet/src/locales/pt.locale.ts
@@ -96,6 +96,7 @@ export default {
verify_instructions:
'Para verificar a atualização, abra o terminal e siga as instruções abaixo:',
},
+ assets: 'Ativos',
no_data: 'Nenhum dado disponível.',
no_matching_results: 'Nenhum resultado encontrado para `{search}`.',
add_new_label: 'Adicionar novo rótulo: {label}',
@@ -125,7 +126,12 @@ export default {
icp: {
name: 'Internet Computer',
standards: {
- native: 'Nativo',
+ icp_native: 'ICP (Nativo)',
+ icrc1: 'ICRC-1',
+ },
+ formats: {
+ icp_account_identifier: 'ICP Nativo',
+ icrc1_account: 'ICRC-1',
},
},
eth: {
@@ -178,6 +184,7 @@ export default {
system: 'Sistema',
transfers: 'Transferências',
users: 'Usuários',
+ assets: 'Ativos',
external_canisters: 'Canisters',
},
download: {
@@ -274,6 +281,9 @@ export default {
editaccount: {
title: 'Editar conta',
request_title: 'Pedido de alteração de conta',
+ added_assets: 'Adicionado',
+ removed_assets: 'Removido',
+ replaced_assets: 'Substituído',
},
editaddressbookentry: {
title: 'Editar endereço',
@@ -291,6 +301,18 @@ export default {
title: 'Gerir informações do sistema',
request_title: 'Pedido de alteração de informações do sistema',
},
+ addasset: {
+ title: 'Adicionar ativo',
+ request_title: 'Pedido de adição de ativo',
+ },
+ editasset: {
+ title: 'Editar ativo',
+ request_title: 'Pedido de alteração de ativo',
+ },
+ removeasset: {
+ title: 'Remover ativo',
+ request_title: 'Pedido de remoção de ativo',
+ },
createexternalcanister: {
title: 'Criar canister',
request_title: 'Pedido de criação de canister',
@@ -686,6 +708,7 @@ export default {
settings: 'Configuraçōes',
close: 'Fechar',
transfer: 'Transferência',
+ transfer_asset: 'Transferir {asset}',
general: 'Geral',
update: 'Atualizar',
time: 'Horário',
@@ -713,6 +736,9 @@ export default {
version: 'Versão',
continue: 'Continuar',
cycle_obtain_strategy: 'Método de recarga da carteira',
+ symbol: 'Símbolo',
+ standards: 'Padrões',
+ assets: 'Ativos',
},
forms: {
create: 'Criar',
@@ -735,11 +761,15 @@ export default {
numberRange: 'Este campo deve estar entre {min} e {max}.',
invalidDecimalPlaces: 'Este campo deve ter no máximo {decimals} casas decimais.',
isHex: 'Este campo deve conter um valor hexadecimal válido.',
+ validAddress: 'Este campo deve conter um endereço válido.',
+ validSymbol: 'O símbolo deve ter de 1 a 32 caracteres alfanuméricos.',
},
},
navigation: {
home: 'Início',
+ dashboard: 'Dashboard',
accounts: 'Contas',
+ account: 'Conta',
address_book: 'Endereços',
users: 'Usuários',
settings: 'Configuraçōes',
@@ -753,9 +783,14 @@ export default {
transfer_requests: 'Pedidos de transferência',
permissions: 'Permissões',
request_policies: 'Regras de aprovação',
+ assets: 'Ativos',
external_canisters: 'Canisters',
},
pages: {
+ dashboard: {
+ title: 'Dashboard',
+ available_assets: 'Ativos disponíveis',
+ },
accounts: {
title: 'Contas',
btn_new_transfer: 'Nova transferência',
@@ -777,6 +812,14 @@ export default {
csv_ignored_transfers_hint: 'Transferências com erros serão ignoradas.',
csv_transfer_failed: 'Error ao processar transferências, por favor, tente novamente.',
csv_download_invalid: 'Baixar erros',
+ add_asset: 'Adicionar ativo',
+ remove_asset: 'Remover ativo',
+ no_assets_to_add: 'Nenhum ativo disponível para adicionar.',
+ remove_asset_confirm:
+ 'Tem a certeza que deseja remover este ativo? Remover o ativo não afeta o saldo da conta. Re-adicionar o ativo restaurará o acesso ao saldo.',
+ transfers_not_supported: 'As transferências não são suportadas para este ativo.',
+ add_index_canister_to_see_transactions:
+ 'Considere adicionar o canister de índice ao ativo para ver as transações.',
},
address_book: {
title: 'Livro de endereços',
@@ -879,6 +922,17 @@ export default {
create_label: 'Criar Regra',
dialog_title: 'Regra',
},
+ assets: {
+ title: 'Ativos',
+ btn_new_entry: 'Novo ativo',
+ no_results_found: 'Nenhum ativo encontrado.',
+ error_fetching_assets: 'Erro ao carregar os ativos, por favor, tente novamente.',
+ forms: {
+ ledger_canister_id: 'ID do canister de contabilidade',
+ index_canister_id: 'ID do canister de índice',
+ decimals: 'Decimais',
+ },
+ },
not_found: {
title: 'Ups, 404',
subtitle: 'A página que está a tentar aceder não existe.',
@@ -918,6 +972,7 @@ export default {
select_resource: 'Selecione o tipo de recurso',
resources: {
account: 'Conta',
+ asset: 'Ativo',
user: 'Usuário',
usergroup: 'Grupo de usuários',
permission: 'Regra de acesso',
@@ -995,6 +1050,9 @@ export default {
fundexternalcanister: 'Financiar canister',
setdisasterrecovery: 'Recuperação de sistema',
callexternalcanister: 'Interagir com canister',
+ addasset: 'Adicionar ativo',
+ editasset: 'Editar ativo',
+ removeasset: 'Remover ativo',
},
},
cycle_obtain_strategies: {
diff --git a/apps/wallet/src/mappers/permissions.mapper.ts b/apps/wallet/src/mappers/permissions.mapper.ts
index 179b53104..bde3bbb88 100644
--- a/apps/wallet/src/mappers/permissions.mapper.ts
+++ b/apps/wallet/src/mappers/permissions.mapper.ts
@@ -43,6 +43,10 @@ export const fromResourceToResourceEnum = (resource: Resource): ResourceTypeEnum
return ResourceTypeEnum.Notification;
}
+ if (variantIs(resource, 'Asset')) {
+ return ResourceTypeEnum.Asset;
+ }
+
return unreachable(resource);
};
diff --git a/apps/wallet/src/mappers/request-specifiers.mapper.ts b/apps/wallet/src/mappers/request-specifiers.mapper.ts
index 2cb33965f..189b629bf 100644
--- a/apps/wallet/src/mappers/request-specifiers.mapper.ts
+++ b/apps/wallet/src/mappers/request-specifiers.mapper.ts
@@ -91,6 +91,18 @@ export const mapRequestSpecifierToEnum = (specifier: RequestSpecifier): RequestS
return RequestSpecifierEnum.SetDisasterRecovery;
}
+ if (variantIs(specifier, 'AddAsset')) {
+ return RequestSpecifierEnum.AddAsset;
+ }
+
+ if (variantIs(specifier, 'EditAsset')) {
+ return RequestSpecifierEnum.EditAsset;
+ }
+
+ if (variantIs(specifier, 'RemoveAsset')) {
+ return RequestSpecifierEnum.RemoveAsset;
+ }
+
return unreachable(specifier);
};
diff --git a/apps/wallet/src/mappers/requests.mapper.ts b/apps/wallet/src/mappers/requests.mapper.ts
index cd7c4f9d0..1bba7ffcd 100644
--- a/apps/wallet/src/mappers/requests.mapper.ts
+++ b/apps/wallet/src/mappers/requests.mapper.ts
@@ -13,6 +13,7 @@ import {
RequestWithDetails,
} from '~/types/requests.types';
import { RequestOperationEnum, RequestStatusEnum } from '~/types/station.types';
+import { detectAddressFormat } from '~/utils/asset.utils';
import { formatBalance, stringify, unreachable, variantIs } from '~/utils/helper.utils';
export const mapRequestsOperationTypeToGroup = (
@@ -79,6 +80,14 @@ export const mapRequestsOperationTypeToGroup = (
return ListRequestsOperationTypeGroup.ExternalCanister;
}
+ if (
+ variantIs(operationType, 'AddAsset') ||
+ variantIs(operationType, 'EditAsset') ||
+ variantIs(operationType, 'RemoveAsset')
+ ) {
+ return ListRequestsOperationTypeGroup.Asset;
+ }
+
return unreachable(operationType);
};
@@ -241,6 +250,15 @@ export const mapRequestOperationToTypeEnum = (
if (variantIs(operation, 'SetDisasterRecovery')) {
return RequestOperationEnum.SetDisasterRecovery;
}
+ if (variantIs(operation, 'AddAsset')) {
+ return RequestOperationEnum.AddAsset;
+ }
+ if (variantIs(operation, 'EditAsset')) {
+ return RequestOperationEnum.EditAsset;
+ }
+ if (variantIs(operation, 'RemoveAsset')) {
+ return RequestOperationEnum.RemoveAsset;
+ }
return unreachable(operation);
};
@@ -310,6 +328,12 @@ export const mapRequestOperationToListRequestsOperationType = (
return { FundExternalCanister: [] };
} else if (variantIs(requestOperation, 'SetDisasterRecovery')) {
return { SetDisasterRecovery: null };
+ } else if (variantIs(requestOperation, 'AddAsset')) {
+ return { AddAsset: null };
+ } else if (variantIs(requestOperation, 'EditAsset')) {
+ return { EditAsset: null };
+ } else if (variantIs(requestOperation, 'RemoveAsset')) {
+ return { RemoveAsset: null };
} else {
return unreachable(requestOperation);
}
@@ -421,10 +445,9 @@ const mapRequestToAccountCsvRow = (request: Request): CsvRow => {
return {
account_id: request.operation.AddAccount.account?.[0]?.id ?? '',
account_name: request.operation.AddAccount.input.name,
- blockchain: request.operation.AddAccount.input.blockchain,
details: stringify({
metadata: request.operation.AddAccount.input.metadata,
- standard: request.operation.AddAccount.input.standard,
+ assets: request.operation.AddAccount.input.assets,
configs_request_policy: request.operation.AddAccount.input.configs_request_policy,
transfer_request_policy: request.operation.AddAccount.input.transfer_request_policy,
}),
@@ -480,18 +503,31 @@ const mapRequestToTransferCsvRow = (request: Request): CsvRow => {
if (variantIs(request.operation, 'Transfer') && request.operation.Transfer.from_account?.[0]) {
const account = request.operation.Transfer.from_account[0];
+ const asset = request.operation.Transfer.from_asset;
+
+ // to determine the `from address` we find a matching address to the format of the `to address`
+ const maybeToAddressFormat = detectAddressFormat(
+ asset.blockchain,
+ request.operation.Transfer.input.to,
+ );
+
+ const fallbackAddress = account.addresses[0]?.address ?? '-';
+
+ const fromAddress = maybeToAddressFormat
+ ? (account.addresses.find(accountAddress => accountAddress.format === maybeToAddressFormat)
+ ?.address ?? fallbackAddress)
+ : fallbackAddress;
+
return {
from_account: account.name,
- from_account_address: account.address,
+ from_account_address: fromAddress,
+ from_asset: `${asset.name} (${asset.blockchain} / ${asset.name})`,
to: request.operation.Transfer.input.to,
amount:
- formatBalance(request.operation.Transfer.input.amount, account.decimals) +
- ' ' +
- account.symbol,
+ formatBalance(request.operation.Transfer.input.amount, asset.decimals) + ' ' + asset.symbol,
fee: request.operation.Transfer.fee[0]
- ? formatBalance(request.operation.Transfer.fee[0], account.decimals) + ' ' + account.symbol
+ ? formatBalance(request.operation.Transfer.fee[0], asset.decimals) + ' ' + asset.symbol
: '',
- // comment: request.summary[0] ?? '',
comment: request.summary[0] ?? '',
};
}
diff --git a/apps/wallet/src/pages/AccountAssetPage.vue b/apps/wallet/src/pages/AccountAssetPage.vue
new file mode 100644
index 000000000..b03cc78c2
--- /dev/null
+++ b/apps/wallet/src/pages/AccountAssetPage.vue
@@ -0,0 +1,515 @@
+
+ {
+ account = result.account;
+ privileges = result.privileges;
+ }
+ "
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + {{ $t('pages.accounts.btn_new_transfer') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('pages.account.remove_asset') }}
+
+
+
+
+
+
+
+ {{ $t('pages.account.not_found_description') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('terms.time') }} |
+
+ {{ $t('app.destination_source') }}
+ |
+
+ {{ $t('app.amount_token', { token: asset?.symbol || '' }) }}
+ |
+
+
+
+
+ {{ $t('app.no_transfers') }} |
+
+
+
+ {{
+ `${transfer.created_at?.toLocaleDateString()} ${transfer.created_at?.toLocaleTimeString()}`
+ }}
+ |
+
+
+
+
+
+
+ |
+
+ {{ asset ? formatBalance(transfer.amount, asset.decimals) : '' }}
+
+
+
+ |
+
+
+
+
+
+
+ {{ $t('pages.account.transfers_not_supported') }}
+
+
+ {{ $t('pages.account.add_index_canister_to_see_transactions') }}
+
+
+
+
+
+
+
+
+ {{ $t('terms.reset') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/pages/AccountPage.vue b/apps/wallet/src/pages/AccountPage.vue
index f5a3a2a7f..07530ac85 100644
--- a/apps/wallet/src/pages/AccountPage.vue
+++ b/apps/wallet/src/pages/AccountPage.vue
@@ -43,25 +43,11 @@
{{ $t('terms.settings') }}
-
-
-
-
-
-
-
- + {{ $t('pages.accounts.btn_new_transfer') }}
-
+
+
+
+ + {{ $t('pages.account.add_asset') }}
+
@@ -72,7 +58,7 @@
class="mb-4"
:see-all-link="{
name: Routes.Requests,
- query: { group_by: RequestDomains.Transfers },
+ query: { group_by: RequestDomains.Accounts },
}"
:types="[{ Transfer: [account.id] }]"
hide-not-found
@@ -84,71 +70,68 @@
class="d-flex flex-column-reverse flex-md-row ga-4 px-0 align-md-start pt-0"
>
- {
+ $router.push({
+ name: Routes.AccountAsset,
+ params: { assetId: item.asset_id },
+ });
+ }
+ "
>
-
-
-
-
- {{ $t('terms.time') }} |
-
- {{ $t('app.destination_source') }}
- |
-
- {{ $t('app.amount_token', { token: account.symbol }) }}
- |
-
-
-
-
- {{ $t('app.no_transfers') }} |
-
-
-
- {{
- `${transfer.created_at?.toLocaleDateString()} ${transfer.created_at?.toLocaleTimeString()}`
- }}
- |
-
-
-
-
-
- |
-
- {{ formatBalance(transfer.amount, account.decimals) }}
-
-
-
- |
-
-
-
-
+
+
+
+
+
+ {{ column.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ assetById(asset.asset_id)?.symbol || '-' }}
+
+
+
+ {{ assetById(asset.asset_id)?.name || 'Unknown asset' }}
+
+
+
+ {{
+ asset.balance[0]
+ ? formatBalance(asset.balance[0].balance, asset.balance[0].decimals)
+ : ''
+ }}
+
+
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/pages/DashboardPage.vue b/apps/wallet/src/pages/DashboardPage.vue
new file mode 100644
index 000000000..015aa4fb4
--- /dev/null
+++ b/apps/wallet/src/pages/DashboardPage.vue
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('pages.dashboard.available_assets') }}
+
+
+ {
+ assets = result.assets;
+ privileges = result.privileges;
+ }
+ "
+ >
+
+
+
+
+
+ {{ asset.symbol }}
+
+ {{ asset.name }}
+
+
+
+
+ {{ $t(`blockchains.${asset.blockchain}.name`) }}
+
+
+
+ {{ formatBalance(asset.totalBalance, asset.decimals) }} {{ asset.symbol }}
+
+
+
+
+ {
+ $router.push({
+ name: Routes.AccountAsset,
+ params: {
+ id: item.item.account.id,
+ assetId: item.item.asset_id,
+ },
+ });
+ }
+ "
+ >
+
+
+
+
+
+ {{ column.title }}
+
+
+
+
+ {{
+ formatBalance(
+ accountAsset.balance[0] ? accountAsset.balance[0].balance : 0n,
+ asset.decimals,
+ )
+ }}
+ {{ asset.symbol }}
+
+
+
+
+
+ {{ $t('pages.accounts.btn_new_transfer') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/wallet/src/plugins/navigation.plugin.ts b/apps/wallet/src/plugins/navigation.plugin.ts
index 2781f9e04..77149231a 100644
--- a/apps/wallet/src/plugins/navigation.plugin.ts
+++ b/apps/wallet/src/plugins/navigation.plugin.ts
@@ -4,6 +4,7 @@ import {
mdiDatabase,
mdiFormatListText,
mdiPlusBox,
+ mdiViewDashboard,
mdiWalletBifold,
} from '@mdi/js';
import { App, Ref, computed, ref, watch } from 'vue';
@@ -38,6 +39,19 @@ const sections = (): NavigationSections => ({
},
icon: mdiPlusBox,
},
+ {
+ name: 'dashboard',
+ localeKey: 'navigation.dashboard',
+ action: {
+ type: NavigationActionType.To,
+ handle: route => (route.params.locale ? `/${route.params.locale}/dashboard` : '/dashboard'),
+ },
+ auth: {
+ type: NavigastionAuthType.Route,
+ route: Routes.Dashboard,
+ },
+ icon: mdiViewDashboard,
+ },
{
name: 'accounts',
localeKey: 'navigation.accounts',
@@ -177,6 +191,19 @@ const sections = (): NavigationSections => ({
route: Routes.Requests,
},
},
+ {
+ name: 'assets',
+ localeKey: 'navigation.assets',
+ action: {
+ type: NavigationActionType.To,
+ handle: route =>
+ route.params.locale ? `/${route.params.locale}/settings/assets` : '/settings/assets',
+ },
+ auth: {
+ type: NavigastionAuthType.Route,
+ route: Routes.Assets,
+ },
+ },
],
},
],
diff --git a/apps/wallet/src/plugins/router.plugin.ts b/apps/wallet/src/plugins/router.plugin.ts
index 03b65b087..02679d596 100644
--- a/apps/wallet/src/plugins/router.plugin.ts
+++ b/apps/wallet/src/plugins/router.plugin.ts
@@ -18,6 +18,7 @@ import { hasRequiredPrivilege, hasRequiredSession } from '~/utils/auth.utils';
import { i18n, i18nRouteGuard } from './i18n.plugin';
import { initStateGuard } from './pinia.plugin';
import { services } from './services.plugin';
+import DashboardPage from '~/pages/DashboardPage.vue';
export const redirectToKey = 'redirectTo';
@@ -53,6 +54,18 @@ const router = createRouter({
},
},
},
+ {
+ path: 'dashboard',
+ name: Routes.Dashboard,
+ component: DashboardPage,
+ meta: {
+ auth: {
+ check: {
+ session: RequiredSessionState.ConnectedToStation,
+ },
+ },
+ },
+ },
{
path: 'accounts',
component: RouterView,
@@ -87,16 +100,7 @@ const router = createRouter({
},
{
path: ':id',
- name: Routes.Account,
- component: () => import('~/pages/AccountPage.vue'),
- props: () => {
- return {
- breadcrumbs: [
- { title: i18n.global.t('navigation.home'), to: { name: defaultHomeRoute } },
- { title: i18n.global.t('navigation.accounts'), to: { name: Routes.Accounts } },
- ],
- };
- },
+ component: RouterView,
meta: {
auth: {
check: {
@@ -105,6 +109,44 @@ const router = createRouter({
},
},
},
+ children: [
+ {
+ path: '',
+ name: Routes.Account,
+ component: () => import('~/pages/AccountPage.vue'),
+ props: () => {
+ return {
+ breadcrumbs: [
+ { title: i18n.global.t('navigation.home'), to: { name: defaultHomeRoute } },
+ {
+ title: i18n.global.t('navigation.accounts'),
+ to: { name: Routes.Accounts },
+ },
+ ],
+ };
+ },
+ },
+ {
+ path: ':assetId',
+ name: Routes.AccountAsset,
+ component: () => import('~/pages/AccountAssetPage.vue'),
+ props: params => {
+ return {
+ breadcrumbs: [
+ { title: i18n.global.t('navigation.home'), to: { name: defaultHomeRoute } },
+ {
+ title: i18n.global.t('navigation.accounts'),
+ to: { name: Routes.Accounts },
+ },
+ {
+ title: i18n.global.t('navigation.account'),
+ to: { name: Routes.Account, params: { id: params.params.id } },
+ },
+ ],
+ };
+ },
+ },
+ ],
},
],
},
@@ -395,6 +437,29 @@ const router = createRouter({
},
},
},
+ {
+ path: 'assets',
+ name: Routes.Assets,
+ component: () => import('~/pages/AssetsPage.vue'),
+ props: () => {
+ return {
+ title: i18n.global.t('pages.assets.title'),
+ breadcrumbs: [
+ { title: i18n.global.t('navigation.home'), to: { name: defaultHomeRoute } },
+ { title: i18n.global.t('navigation.settings') },
+ { title: i18n.global.t('navigation.assets') },
+ ],
+ };
+ },
+ meta: {
+ auth: {
+ check: {
+ session: RequiredSessionState.ConnectedToStation,
+ privileges: [Privilege.ListAssets],
+ },
+ },
+ },
+ },
],
},
{
diff --git a/apps/wallet/src/services/chains/ic-native-api.service.ts b/apps/wallet/src/services/chains/ic-native-api.service.ts
index e79d3daa0..c08924b95 100644
--- a/apps/wallet/src/services/chains/ic-native-api.service.ts
+++ b/apps/wallet/src/services/chains/ic-native-api.service.ts
@@ -1,43 +1,68 @@
import { Actor, ActorSubclass, HttpAgent } from '@dfinity/agent';
-import { appInitConfig } from '~/configs/init.config';
import { icAgent } from '~/core/ic-agent.core';
-import { idlFactory } from '~/generated/icp_index';
-import { _SERVICE } from '~/generated/icp_index/icp_index.did';
-import { Account } from '~/generated/station/station.did';
-import { AccountIncomingTransfer, ChainApi, FetchTransfersInput } from '~/types/chain.types';
+import { idlFactory as IcpIndexIdlFactory } from '~/generated/icp_index';
+import { idlFactory as IcpLedgerIdlFactory } from '~/generated/icp_ledger';
+import { _SERVICE as IcpIndexService } from '~/generated/icp_index/icp_index.did';
+import { _SERVICE as IcpLedgerService } from '~/generated/icp_ledger/icp_ledger.did';
+import {
+ AccountIncomingTransfer,
+ ChainApi,
+ ChainApiCapability,
+ FetchTransfersInput,
+} from '~/types/chain.types';
import { nanoToJsDate } from '~/utils/date.utils';
-import { isValidSha256 } from '~/utils/helper.utils';
+import { hexStringToUint8Array, isValidSha256 } from '~/utils/helper.utils';
export class ICNativeApi implements ChainApi {
- private actor: ActorSubclass<_SERVICE>;
+ private indexActor: ActorSubclass | null = null;
+ private ledgerActor: ActorSubclass;
static PAGE_SIZE = BigInt(100);
constructor(
- private readonly account: Account,
+ private readonly address: string,
+ private readonly ledgerCanisterId: string,
+ private readonly indexCanisterId: string | undefined,
agent: HttpAgent = icAgent.get(),
) {
- this.actor = Actor.createActor<_SERVICE>(idlFactory, {
+ if (this.indexCanisterId) {
+ this.indexActor = Actor.createActor(IcpIndexIdlFactory, {
+ agent,
+ canisterId: this.indexCanisterId,
+ });
+ }
+
+ this.ledgerActor = Actor.createActor(IcpLedgerIdlFactory, {
agent,
- canisterId: appInitConfig.canisters.icpIndex,
+ canisterId: this.ledgerCanisterId,
});
}
- isValidAddress(address: string): boolean {
+ static isValidAddress(address: string): boolean {
return isValidSha256(address);
}
+ isValidAddress(address: string): boolean {
+ return ICNativeApi.isValidAddress(address);
+ }
+
async fetchBalance(): Promise {
- const balance = await this.actor.get_account_identifier_balance(this.account.address);
+ const balance = await this.ledgerActor.account_balance({
+ account: hexStringToUint8Array(this.address),
+ });
- return balance;
+ return balance.e8s;
}
async fetchTransfers(
input: FetchTransfersInput,
startBlockId?: bigint,
): Promise {
- const result = await this.actor.get_account_identifier_transactions({
- account_identifier: this.account.address,
+ if (!this.indexActor) {
+ throw new Error('Cannot fetch balance without index canister id.');
+ }
+
+ const result = await this.indexActor.get_account_identifier_transactions({
+ account_identifier: this.address,
start: startBlockId ? [startBlockId] : [],
max_results: ICNativeApi.PAGE_SIZE,
});
@@ -95,4 +120,11 @@ export class ICNativeApi implements ChainApi {
return transfers;
}
+
+ getCapabilities(): ChainApiCapability[] {
+ return [
+ ChainApiCapability.Balance, // balance always available due to ledger canister id mandatory
+ ...(this.indexActor ? [ChainApiCapability.Transfers] : []),
+ ];
+ }
}
diff --git a/apps/wallet/src/services/chains/icrc1-api.service.ts b/apps/wallet/src/services/chains/icrc1-api.service.ts
new file mode 100644
index 000000000..59cca23e6
--- /dev/null
+++ b/apps/wallet/src/services/chains/icrc1-api.service.ts
@@ -0,0 +1,144 @@
+import { Actor, ActorSubclass, HttpAgent } from '@dfinity/agent';
+import { icAgent } from '~/core/ic-agent.core';
+import {
+ AccountIncomingTransfer,
+ ChainApi,
+ ChainApiCapability,
+ FetchTransfersInput,
+} from '~/types/chain.types';
+import { nanoToJsDate } from '~/utils/date.utils';
+import { decodeIcrcAccount, encodeIcrcAccount } from '@dfinity/ledger-icrc';
+import { Account } from '~/generated/icp_index/icp_index.did';
+import { idlFactory as Icrc1IndexIdlFactory } from '~/generated/icrc1_index';
+import { idlFactory as Icrc1LedgerIdlFactory } from '~/generated/icrc1_ledger';
+import { _SERVICE as Icrc1IndexService } from '~/generated/icrc1_index/icrc1_index_canister.did';
+import { _SERVICE as Icrc1LedgerService } from '~/generated/icrc1_ledger/icrc1_ledger_canister.did';
+
+export class ICRC1Api implements ChainApi {
+ private indexActor: ActorSubclass | null = null;
+ private ledgerActor: ActorSubclass;
+ static PAGE_SIZE = BigInt(100);
+
+ private account: Account;
+
+ constructor(
+ address: string,
+ private readonly ledgerCanisterId: string,
+ private readonly indexCanisterId: string | undefined,
+ agent: HttpAgent = icAgent.get(),
+ ) {
+ const icrc1Account = decodeIcrcAccount(address);
+
+ this.account = {
+ owner: icrc1Account.owner,
+ subaccount: icrc1Account.subaccount ? [icrc1Account.subaccount] : [],
+ };
+
+ if (this.indexCanisterId) {
+ this.indexActor = Actor.createActor(Icrc1IndexIdlFactory, {
+ agent,
+ canisterId: this.indexCanisterId,
+ });
+ }
+
+ this.ledgerActor = Actor.createActor(Icrc1LedgerIdlFactory, {
+ agent,
+ canisterId: this.ledgerCanisterId,
+ });
+ }
+
+ static isValidAddress(address: string): boolean {
+ try {
+ decodeIcrcAccount(address);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+ isValidAddress(address: string): boolean {
+ return ICRC1Api.isValidAddress(address);
+ }
+
+ async fetchBalance(): Promise {
+ return await this.ledgerActor.icrc1_balance_of(this.account);
+ }
+
+ async fetchTransfers(
+ input: FetchTransfersInput,
+ startBlockId?: bigint,
+ ): Promise {
+ if (!this.indexActor) {
+ throw new Error('Cannot fetch balance without index canister id.');
+ }
+
+ const result = await this.indexActor.get_account_transactions({
+ account: this.account,
+ max_results: ICRC1Api.PAGE_SIZE,
+ start: startBlockId ? [startBlockId] : [],
+ });
+
+ if ('Err' in result) {
+ throw result.Err;
+ }
+
+ const response = result.Ok;
+ let transfers: AccountIncomingTransfer[] = [];
+ let nextTxId: null | bigint = null;
+ if (response.transactions.length) {
+ const lastTx = response.transactions[response.transactions.length - 1];
+ nextTxId = lastTx.id;
+ }
+ response.transactions.forEach(tx => {
+ if (tx.transaction.transfer[0]) {
+ const transferInfo = tx.transaction.transfer[0];
+
+ transfers.push({
+ from: encodeIcrcAccount({
+ owner: transferInfo.from.owner,
+ subaccount: transferInfo.from.subaccount[0],
+ }),
+ to: encodeIcrcAccount({
+ owner: transferInfo.to.owner,
+ subaccount: transferInfo.to.subaccount[0],
+ }),
+ amount: transferInfo.amount,
+ fee: transferInfo.fee[0] ?? 0n,
+ created_at: nanoToJsDate(tx.transaction.timestamp),
+ });
+ }
+ });
+
+ if (
+ transfers.length &&
+ transfers[transfers.length - 1]?.created_at &&
+ nextTxId !== null &&
+ nextTxId !== response.oldest_tx_id?.[0]
+ ) {
+ const lastTransfer = transfers[transfers.length - 1];
+ const lastTransferTime = lastTransfer.created_at!.getTime();
+ const shouldFetchMore =
+ (input.fromDt && lastTransferTime > input.fromDt!.getTime()) || (!input.fromDt && nextTxId);
+
+ if (shouldFetchMore) {
+ const moreTransfers = await this.fetchTransfers(input, nextTxId);
+ transfers.push(...moreTransfers);
+ }
+ }
+
+ transfers = transfers.filter(t => {
+ const isInFromDt = !input.fromDt ? true : t.created_at && t.created_at >= input.fromDt;
+ const isInToDt = !input.toDt ? true : t.created_at && t.created_at <= input.toDt;
+
+ return isInFromDt && isInToDt;
+ });
+
+ return transfers;
+ }
+
+ getCapabilities(): ChainApiCapability[] {
+ return [
+ ChainApiCapability.Balance, // balance always available due to ledger canister id mandatory
+ ...(this.indexActor ? [ChainApiCapability.Transfers] : []),
+ ];
+ }
+}
diff --git a/apps/wallet/src/services/chains/index.ts b/apps/wallet/src/services/chains/index.ts
index 4a022f024..f524f8c54 100644
--- a/apps/wallet/src/services/chains/index.ts
+++ b/apps/wallet/src/services/chains/index.ts
@@ -1,16 +1,48 @@
-import { Account } from '~/generated/station/station.did';
-import { BlockchainStandard, BlockchainType, ChainApi } from '~/types/chain.types';
+import { AccountAddress, Asset } from '~/generated/station/station.did';
+import { AddressFormat, BlockchainStandard, BlockchainType, ChainApi } from '~/types/chain.types';
+import { getAssetMetadata } from '~/utils/asset.utils';
import { ICNativeApi } from './ic-native-api.service';
+import { ICRC1Api } from './icrc1-api.service';
export class ChainApiFactory {
- static create(account: Account): ChainApi {
- const chainAndStandard = `${account.blockchain}-${account.standard}`;
+ static create(asset: Asset, addresses: AccountAddress[]): ChainApi {
+ switch (asset.blockchain) {
+ case BlockchainType.InternetComputer: {
+ const maybeIcpNativeAddress = addresses.find(a => a.format === AddressFormat.ICPNative);
+ const maybeIcrc1Address = addresses.find(a => a.format === AddressFormat.ICRC1);
+ const maybeLedgerCanisterId = getAssetMetadata(asset, 'ledger_canister_id');
+ const maybeIndexCanisterId = getAssetMetadata(asset, 'index_canister_id');
- switch (chainAndStandard) {
- case `${BlockchainType.InternetComputer}-${BlockchainStandard.Native}`:
- return new ICNativeApi(account);
+ if (
+ asset.standards.includes(BlockchainStandard.Native) &&
+ maybeIcpNativeAddress &&
+ maybeLedgerCanisterId
+ ) {
+ return new ICNativeApi(
+ maybeIcpNativeAddress.address,
+ maybeLedgerCanisterId,
+ maybeIndexCanisterId,
+ );
+ }
+
+ if (
+ asset.standards.includes(BlockchainStandard.ICRC1) &&
+ maybeIcrc1Address &&
+ maybeLedgerCanisterId
+ ) {
+ return new ICRC1Api(
+ maybeIcrc1Address.address,
+ maybeLedgerCanisterId,
+ maybeIndexCanisterId,
+ );
+ }
+
+ throw new Error(`Blockchain not supported: ${asset.blockchain}`);
+ }
+ case BlockchainType.Bitcoin:
+ case BlockchainType.Ethereum:
default:
- throw new Error(`Blockchain not supported ${chainAndStandard}`);
+ throw new Error(`Blockchain not supported: ${asset.blockchain}`);
}
}
}
diff --git a/apps/wallet/src/services/station.service.ts b/apps/wallet/src/services/station.service.ts
index 3a6e66aff..d497586d7 100644
--- a/apps/wallet/src/services/station.service.ts
+++ b/apps/wallet/src/services/station.service.ts
@@ -2,9 +2,11 @@ import { Actor, ActorSubclass, HttpAgent } from '@dfinity/agent';
import { Principal } from '@dfinity/principal';
import { idlFactory } from '~/generated/station';
import {
- AccountBalance,
+ Account,
+ AccountCallerPrivileges,
AddAccountOperationInput,
AddAddressBookEntryOperationInput,
+ AddAssetOperationInput,
AddRequestPolicyOperationInput,
AddUserGroupOperationInput,
AddUserOperationInput,
@@ -20,16 +22,20 @@ import {
DisasterRecoveryCommittee,
EditAccountOperationInput,
EditAddressBookEntryOperationInput,
+ EditAssetOperationInput,
EditPermissionOperationInput,
EditRequestPolicyOperationInput,
EditUserGroupOperationInput,
EditUserOperationInput,
FetchAccountBalancesInput,
+ FetchAccountBalancesResult,
FundExternalCanisterOperationInput,
GetAccountInput,
GetAccountResult,
GetAddressBookEntryInput,
GetAddressBookEntryResult,
+ GetAssetInput,
+ GetAssetResult,
GetExternalCanisterFiltersResult,
GetExternalCanisterResult,
GetNextApprovableRequestResult,
@@ -46,6 +52,7 @@ import {
ListAccountTransfersInput,
ListAccountsResult,
ListAddressBookEntriesResult,
+ ListAssetsResult,
ListExternalCanistersResult,
ListNotificationsInput,
ListPermissionsInput,
@@ -59,6 +66,7 @@ import {
MarkNotificationsReadInput,
Notification,
PaginationInput,
+ RemoveAssetOperationInput,
RemoveUserGroupOperationInput,
Request,
SubmitRequestApprovalInput,
@@ -78,6 +86,7 @@ import {
GetNextApprovableRequestArgs,
ListAccountsArgs,
ListAddressBookEntriesArgs,
+ ListAssetsArgs,
ListExternalCanistersArgs,
ListRequestsArgs,
} from '~/types/station.types';
@@ -548,8 +557,50 @@ export class StationService {
return result.Ok;
}
+ async listAllAccounts(verifiedCall = false): Promise<{
+ accounts: Account[];
+ privileges: AccountCallerPrivileges[];
+ }> {
+ const actor = verifiedCall ? this.verified_actor : this.actor;
+
+ const accounts: Account[] = [];
+ const privileges: AccountCallerPrivileges[] = [];
+ let nextOffset: [bigint] | [] = [];
+
+ do {
+ const result = await actor.list_accounts({
+ paginate: [
+ {
+ limit: [100],
+ offset: nextOffset,
+ },
+ ],
+ search_term: [],
+ });
+
+ if (variantIs(result, 'Err')) {
+ throw result.Err;
+ }
+
+ accounts.push(...result.Ok.accounts);
+ privileges.push(...result.Ok.privileges);
+
+ nextOffset = result.Ok.next_offset as [bigint] | []; // have to force cast here because of typescript inference
+ } while (nextOffset.length > 0);
+
+ return { accounts, privileges };
+ }
+
async listAddressBook(
- { limit, offset, blockchain, labels, ids, addresses }: ListAddressBookEntriesArgs = {},
+ {
+ limit,
+ offset,
+ blockchain,
+ labels,
+ ids,
+ addresses,
+ address_formats,
+ }: ListAddressBookEntriesArgs = {},
verifiedCall = false,
): Promise> {
const actor = verifiedCall ? this.verified_actor : this.actor;
@@ -564,6 +615,7 @@ export class StationService {
labels: labels ? [labels] : [],
addresses: addresses ? [addresses] : [],
ids: ids ? [ids] : [],
+ address_formats: address_formats ? [address_formats] : [],
});
if (variantIs(result, 'Err')) {
@@ -573,6 +625,17 @@ export class StationService {
return result.Ok;
}
+ async getAsset(input: GetAssetInput, verifiedCall = false): Promise> {
+ const actor = verifiedCall ? this.verified_actor : this.actor;
+ const result = await actor.get_asset(input);
+
+ if (variantIs(result, 'Err')) {
+ throw result.Err;
+ }
+
+ return result.Ok;
+ }
+
async fundExternalCanister(input: FundExternalCanisterOperationInput): Promise {
const result = await this.actor.create_request({
execution_plan: [{ Immediate: null }],
@@ -677,6 +740,27 @@ export class StationService {
return result.Ok;
}
+ async listAssets(
+ { limit, offset }: ListAssetsArgs = {},
+ verifiedCall = false,
+ ): Promise> {
+ const actor = verifiedCall ? this.verified_actor : this.actor;
+ const result = await actor.list_assets({
+ paginate: [
+ {
+ limit: limit !== undefined ? [limit] : [],
+ offset: offset !== undefined ? [BigInt(offset)] : [],
+ },
+ ],
+ });
+
+ if (variantIs(result, 'Err')) {
+ throw result.Err;
+ }
+
+ return result.Ok;
+ }
+
async getExternalCanisterByCanisterId(
canisterId: Principal,
verifiedCall = false,
@@ -718,6 +802,22 @@ export class StationService {
return result.Ok;
}
+ async addAsset(input: AddAssetOperationInput): Promise {
+ const result = await this.actor.create_request({
+ execution_plan: [{ Immediate: null }],
+ title: [],
+ summary: [],
+ operation: { AddAsset: input },
+ expiration_dt: [],
+ });
+
+ if (variantIs(result, 'Err')) {
+ throw result.Err;
+ }
+
+ return result.Ok.request;
+ }
+
async fetchExternalCanisterFilters(
args: {
with_labels?: boolean;
@@ -757,6 +857,38 @@ export class StationService {
return result.Ok.request;
}
+ async editAsset(input: EditAssetOperationInput): Promise {
+ const result = await this.actor.create_request({
+ execution_plan: [{ Immediate: null }],
+ title: [],
+ summary: [],
+ operation: { EditAsset: input },
+ expiration_dt: [],
+ });
+
+ if (variantIs(result, 'Err')) {
+ throw result.Err;
+ }
+
+ return result.Ok.request;
+ }
+
+ async removeAsset(input: RemoveAssetOperationInput): Promise {
+ const result = await this.actor.create_request({
+ execution_plan: [{ Immediate: null }],
+ title: [],
+ summary: [],
+ operation: { RemoveAsset: input },
+ expiration_dt: [],
+ });
+
+ if (variantIs(result, 'Err')) {
+ throw result.Err;
+ }
+
+ return result.Ok.request;
+ }
+
async getAccount(
input: GetAccountInput,
verifiedCall = false,
@@ -824,7 +956,9 @@ export class StationService {
return variantIs(result, 'Healthy');
}
- async fetchAccountBalances(input: FetchAccountBalancesInput): Promise {
+ async fetchAccountBalances(
+ input: FetchAccountBalancesInput,
+ ): Promise['balances']> {
const result = await this.actor.fetch_account_balances(input);
if (variantIs(result, 'Err')) {
diff --git a/apps/wallet/src/stores/station.store.ts b/apps/wallet/src/stores/station.store.ts
index d33785318..a3e30cb7c 100644
--- a/apps/wallet/src/stores/station.store.ts
+++ b/apps/wallet/src/stores/station.store.ts
@@ -21,7 +21,7 @@ import { services } from '~/plugins/services.plugin';
import { StationService } from '~/services/station.service';
import { useAppStore } from '~/stores/app.store';
import { Privilege } from '~/types/auth.types';
-import { BlockchainStandard, BlockchainType } from '~/types/chain.types';
+import { BlockchainType } from '~/types/chain.types';
import { LoadableItem } from '~/types/helper.types';
import { computedStationName, isApiError, popRedirectToLocation } from '~/utils/app.utils';
import { hasRequiredPrivilege } from '~/utils/auth.utils';
@@ -73,6 +73,10 @@ export const createUserInitialAccount = async (
userId: UUID,
station = useStationStore(),
): Promise => {
+ const maybeIcpId = station.configuration.details.supported_assets.find(
+ asset => asset.blockchain == BlockchainType.InternetComputer && asset.symbol == 'ICP',
+ )?.id;
+
await station.service.createRequest({
title: [],
summary: [],
@@ -81,8 +85,7 @@ export const createUserInitialAccount = async (
operation: {
AddAccount: {
name: i18n.global.t('app.initial_account_name'),
- blockchain: BlockchainType.InternetComputer,
- standard: BlockchainStandard.Native,
+ assets: maybeIcpId ? [maybeIcpId] : [],
metadata: [],
read_permission: { auth_scope: { Restricted: null }, user_groups: [], users: [userId] },
transfer_permission: {
@@ -124,6 +127,7 @@ const initialStoreState = (): StationStoreState => {
name: '',
version: '',
supported_assets: [],
+ supported_blockchains: [],
},
cycleObtainStrategy: { Disabled: null },
},
diff --git a/apps/wallet/src/types/auth.types.ts b/apps/wallet/src/types/auth.types.ts
index e18c717c6..4f277d99f 100644
--- a/apps/wallet/src/types/auth.types.ts
+++ b/apps/wallet/src/types/auth.types.ts
@@ -15,6 +15,8 @@ export enum Privilege {
ListRequests = 'ListRequests',
SystemUpgrade = 'SystemUpgrade',
ManageSystemInfo = 'ManageSystemInfo',
+ ListAssets = 'ListAssets',
+ AddAsset = 'AddAsset',
ListExternalCanisters = 'ListExternalCanisters',
CreateExternalCanister = 'CreateExternalCanister',
CallAnyExternalCanister = 'CallAnyExternalCanister',
diff --git a/apps/wallet/src/types/chain.types.ts b/apps/wallet/src/types/chain.types.ts
index 4aa6ba37c..b071b0a71 100644
--- a/apps/wallet/src/types/chain.types.ts
+++ b/apps/wallet/src/types/chain.types.ts
@@ -5,7 +5,13 @@ export enum BlockchainType {
}
export enum BlockchainStandard {
- Native = 'native',
+ Native = 'icp_native',
+ ICRC1 = 'icrc1',
+}
+
+export enum AddressFormat {
+ ICPNative = 'icp_account_identifier',
+ ICRC1 = 'icrc1_account',
}
export enum TokenSymbol {
@@ -30,10 +36,17 @@ export interface FetchTransfersResponse {
transfers: AccountIncomingTransfer[];
}
+export enum ChainApiCapability {
+ Balance,
+ Transfers,
+}
+
export interface ChainApi {
fetchBalance(): Promise;
fetchTransfers(input: FetchTransfersInput): Promise;
isValidAddress(address: string): boolean;
+
+ getCapabilities(): ChainApiCapability[];
}
diff --git a/apps/wallet/src/types/permissions.types.ts b/apps/wallet/src/types/permissions.types.ts
index 80ba85a60..a1c58ac27 100644
--- a/apps/wallet/src/types/permissions.types.ts
+++ b/apps/wallet/src/types/permissions.types.ts
@@ -13,6 +13,7 @@ export enum ResourceTypeEnum {
ExternalCanister = 'ExternalCanister',
SetDisasterRecovery = 'SetDisasterRecovery',
Notification = 'Notification',
+ Asset = 'Asset',
}
export enum ResourceActionEnum {
diff --git a/apps/wallet/src/types/requests.types.ts b/apps/wallet/src/types/requests.types.ts
index 3143cf482..e37f5c080 100644
--- a/apps/wallet/src/types/requests.types.ts
+++ b/apps/wallet/src/types/requests.types.ts
@@ -11,6 +11,7 @@ export enum ListRequestsOperationTypeGroup {
SystemUpgrade = 'system_upgrade',
SystemInfo = 'system_info',
ExternalCanister = 'external_canister',
+ Asset = 'asset',
}
export enum RequestApprovalStatusEnum {
diff --git a/apps/wallet/src/types/station.types.ts b/apps/wallet/src/types/station.types.ts
index 0bd8edc06..95b45ab98 100644
--- a/apps/wallet/src/types/station.types.ts
+++ b/apps/wallet/src/types/station.types.ts
@@ -80,6 +80,7 @@ export enum RequestDomains {
Users = 'users',
ExternalCanisters = 'external_canisters',
System = 'system',
+ Assets = 'assets',
}
export interface ListAccountsArgs {
@@ -116,6 +117,9 @@ export enum RequestSpecifierEnum {
CallExternalCanister = 'CallExternalCanister',
FundExternalCanister = 'FundExternalCanister',
SetDisasterRecovery = 'SetDisasterRecovery',
+ AddAsset = 'AddAsset',
+ EditAsset = 'EditAsset',
+ RemoveAsset = 'RemoveAsset',
}
export enum RequestPolicyRuleEnum {
@@ -142,6 +146,12 @@ export interface ListAddressBookEntriesArgs {
blockchain?: string;
labels?: [];
ids?: UUID[];
+ address_formats?: string[];
+}
+
+export interface ListAssetsArgs {
+ limit?: number;
+ offset?: number;
}
export interface ListExternalCanistersArgs {
@@ -186,4 +196,7 @@ export enum RequestOperationEnum {
ConfigureExternalCanister = 'ConfigureExternalCanister',
FundExternalCanister = 'FundExternalCanister',
SetDisasterRecovery = 'SetDisasterRecovery',
+ AddAsset = 'AddAsset',
+ EditAsset = 'EditAsset',
+ RemoveAsset = 'RemoveAsset',
}
diff --git a/apps/wallet/src/utils/asset.utils.spec.ts b/apps/wallet/src/utils/asset.utils.spec.ts
new file mode 100644
index 000000000..e4d021e37
--- /dev/null
+++ b/apps/wallet/src/utils/asset.utils.spec.ts
@@ -0,0 +1,27 @@
+import { shortenIcrc1Address } from './asset.utils';
+import { describe, expect, it } from 'vitest';
+
+describe('shortenIcrc1Address', () => {
+ it('returns the principal if the subaccount is not present', () => {
+ expect(shortenIcrc1Address('rwlgt-iiaaa-aaaaa-aaaaa-cai')).toBe('rwlgt-iiaaa-aaaaa-aaaaa-cai');
+ expect(
+ shortenIcrc1Address('wmzac-nabae-aqcai-baeaq-caiba-eaqca-ibaea-qcaib-aeaqc-aibae-aqc'),
+ ).toBe('wmzac-nabae-...-aibae-aqc');
+ });
+
+ it('returns some of the principal and some of the subaccount if the subaccount is present', () => {
+ expect(
+ shortenIcrc1Address(
+ 'wmzac-nabae-aqcai-baeaq-caiba-eaqca-ibaea-qcaib-aeaqc-aibae-aqc-haltvua.102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+ ),
+ ).toBe('wmzac-...102030405060708090a0...1e1f');
+
+ expect(
+ shortenIcrc1Address(
+ 'rwlgt-iiaaa-aaaaa-aaaaa-cai-pyz4egi.102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+ ),
+ ).toBe('rwlgt-...102030405060708090a0...1e1f');
+
+ expect(shortenIcrc1Address('rwlgt-iiaaa-aaaaa-aaaaa-cai-ltrlami.10203')).toBe('rwlgt-...10203');
+ });
+});
diff --git a/apps/wallet/src/utils/asset.utils.ts b/apps/wallet/src/utils/asset.utils.ts
new file mode 100644
index 000000000..8f6f7f956
--- /dev/null
+++ b/apps/wallet/src/utils/asset.utils.ts
@@ -0,0 +1,72 @@
+import { decodeIcrcAccount } from '@dfinity/ledger-icrc';
+import { Asset, StandardData, SupportedBlockchain } from '~/generated/station/station.did';
+import { ICNativeApi } from '~/services/chains/ic-native-api.service';
+import { ICRC1Api } from '~/services/chains/icrc1-api.service';
+import { AddressFormat, BlockchainType } from '~/types/chain.types';
+
+export function getAssetMetadata(asset: Asset, key: string): string | undefined {
+ return asset.metadata.find(m => m.key === key)?.value;
+}
+
+export function detectAddressFormat(blockchain: string, address: string): string | undefined {
+ switch (blockchain) {
+ case BlockchainType.InternetComputer:
+ if (ICNativeApi.isValidAddress(address)) {
+ return AddressFormat.ICPNative;
+ } else if (ICRC1Api.isValidAddress(address)) {
+ return AddressFormat.ICRC1;
+ } else {
+ return;
+ }
+ case BlockchainType.Bitcoin:
+ case BlockchainType.Ethereum:
+ return;
+ default:
+ throw new Error(`Blockchain not supported ${blockchain}`);
+ }
+}
+
+export function detectAddressStandard(
+ asset: Asset,
+ address: string,
+ supportedBlockchains: SupportedBlockchain[],
+): StandardData | undefined {
+ const maybeFormat = detectAddressFormat(asset.blockchain, address);
+ if (!maybeFormat) {
+ return;
+ }
+
+ const supportedStandards = supportedBlockchains
+ .find(b => b.blockchain === asset.blockchain)
+ ?.supported_standards.filter(supportedStandard =>
+ asset.standards.includes(supportedStandard.standard),
+ );
+
+ return supportedStandards?.find(s => s.supported_address_formats.includes(maybeFormat));
+}
+
+export function shortenIcrc1Address(address: string): string {
+ const account = decodeIcrcAccount(address);
+ const principal = account.owner.toText();
+
+ if (!account.subaccount || account.subaccount.every(b => b === 0)) {
+ // show just the principal, if there is no subaccount
+ if (principal.length <= 32) {
+ // the principal is short enough to show the whole thing
+ return principal;
+ }
+
+ // shorten the principal
+ return principal.slice(0, 12) + '...' + principal.slice(-10);
+ } else {
+ const subaccount = address.split('.')[1];
+
+ if (subaccount.length <= 27) {
+ // the subaccount is short enough to show the whole thing
+ return `${address.slice(0, 6)}...${subaccount}`;
+ }
+
+ // shorted the subaccount
+ return `${address.slice(0, 6)}...${subaccount.slice(0, 20)}...${address.slice(-4)}`;
+ }
+}
diff --git a/apps/wallet/src/utils/form.utils.ts b/apps/wallet/src/utils/form.utils.ts
index 4a254289e..f58d23d07 100644
--- a/apps/wallet/src/utils/form.utils.ts
+++ b/apps/wallet/src/utils/form.utils.ts
@@ -1,6 +1,7 @@
import { Principal } from '@dfinity/principal';
import isUUID from 'validator/es/lib/isUUID';
import { i18n } from '~/plugins/i18n.plugin';
+import { detectAddressFormat } from './asset.utils';
export const requiredRule = (value: unknown): string | boolean => {
if (value === null || value === undefined || value === '') {
@@ -109,6 +110,20 @@ export const maxLengthRule = (max: number, field: string) => {
};
};
+export const validSymbolRule = (value: unknown): string | boolean => {
+ const hasValue = !!value;
+ if (!hasValue) {
+ // this rule only applies if there is a value
+ return true;
+ }
+
+ if (typeof value !== 'string') {
+ throw new Error('validSymbolRule only applies to strings');
+ }
+
+ return /^[a-zA-Z0-9]{1,32}$/.test(value) ? true : i18n.global.t('forms.rules.validSymbol');
+};
+
export const uniqueRule = (
existing: unknown[],
errorMessage: string = i18n.global.t('forms.rules.duplicate'),
@@ -241,3 +256,26 @@ export const validEmail = (value: unknown): string | boolean => {
return true;
};
+
+export const validAddress =
+ (blockchain: string) =>
+ (value: unknown): string | boolean => {
+ const hasValue = !!value;
+ if (!hasValue) {
+ // this rule only applies if there is a value
+ return true;
+ }
+
+ if (typeof value !== 'string') {
+ return i18n.global.t('forms.rules.validAddress');
+ }
+
+ try {
+ if (detectAddressFormat(blockchain, value) !== undefined) {
+ return true;
+ }
+ return i18n.global.t('forms.rules.validAddress');
+ } catch {
+ return i18n.global.t('forms.rules.validAddress');
+ }
+ };
diff --git a/apps/wallet/src/utils/helper.utils.ts b/apps/wallet/src/utils/helper.utils.ts
index 0581a60e7..d3d64b457 100644
--- a/apps/wallet/src/utils/helper.utils.ts
+++ b/apps/wallet/src/utils/helper.utils.ts
@@ -542,6 +542,16 @@ export const transformData = (
return normalizedInput;
};
+export function hexStringToUint8Array(input: string) {
+ const result = new Uint8Array(input.length / 2);
+
+ for (let i = 0; i < input.length; i += 2) {
+ result[i / 2] = parseInt(input.slice(i, i + 2), 16);
+ }
+
+ return result;
+}
+
/**
* Deep clones the input data using structured cloning, if Proxy objects are found they are
* transformed to plain objects.
diff --git a/apps/wallet/src/workers/accounts.worker.ts b/apps/wallet/src/workers/accounts.worker.ts
index 111216e24..4025e6321 100644
--- a/apps/wallet/src/workers/accounts.worker.ts
+++ b/apps/wallet/src/workers/accounts.worker.ts
@@ -1,8 +1,9 @@
import { Principal } from '@dfinity/principal';
import { icAgent } from '~/core/ic-agent.core';
import { logger } from '~/core/logger.core';
-import { AccountBalance, UUID } from '~/generated/station/station.did';
+import { AccountBalance, FetchAccountBalancesResult, UUID } from '~/generated/station/station.did';
import { StationService } from '~/services/station.service';
+import { ExtractOk } from '~/types/helper.types';
import { arrayBatchMaker, timer, unreachable } from '~/utils/helper.utils';
const DEFAULT_INTERVAL_MS = 10000;
@@ -42,7 +43,7 @@ export interface AccountsWorkerErrorResponse {
}
export interface AccountBalancesWorkerResponse {
- balances: AccountBalance[];
+ balances: Array<[] | [AccountBalance]>;
}
export type AccountsWorkerResponseMessage =
@@ -130,7 +131,7 @@ class AccountsWorkerImpl {
this.stationService.fetchAccountBalances({ account_ids: accountIds }).catch(err => {
logger.error('Failed to update the balance for the given account ids', { err });
- return [] as AccountBalance[];
+ return [] as ExtractOk['balances'];
}),
);
diff --git a/core/control-panel/impl/src/controllers/station.rs b/core/control-panel/impl/src/controllers/station.rs
index 1029e3003..a8918b0b2 100644
--- a/core/control-panel/impl/src/controllers/station.rs
+++ b/core/control-panel/impl/src/controllers/station.rs
@@ -130,7 +130,7 @@ impl StationController {
async fn deploy_station(&self, input: DeployStationInput) -> ApiResult {
let ctx = CallContext::get();
let _lock = STATE
- .with(|state| CallerGuard::new(state.clone(), ctx.caller()))
+ .with(|state| CallerGuard::new(state.clone(), ctx.caller(), None))
.ok_or(UserError::ConcurrentStationDeployment)?;
let deployed_station_id = self.deploy_service.deploy_station(input, &ctx).await?;
diff --git a/core/control-panel/impl/src/services/canister.rs b/core/control-panel/impl/src/services/canister.rs
index efb5589b2..705a927eb 100644
--- a/core/control-panel/impl/src/services/canister.rs
+++ b/core/control-panel/impl/src/services/canister.rs
@@ -51,6 +51,7 @@ impl CanisterService {
self.assert_controller(&CallContext::get(), "upload_canister_modules".to_string())?;
let mut config = canister_config().unwrap_or_default();
+
if let Some(upgrader_wasm_module) = input.upgrader_wasm_module {
config.upgrader_wasm_module = upgrader_wasm_module;
}
@@ -60,6 +61,7 @@ impl CanisterService {
if let Some(station_wasm_module_extra_chunks) = input.station_wasm_module_extra_chunks {
config.station_wasm_module_extra_chunks = station_wasm_module_extra_chunks;
}
+
write_canister_config(config);
Ok(())
diff --git a/core/control-panel/impl/src/services/deploy.rs b/core/control-panel/impl/src/services/deploy.rs
index ff1a9b6a5..4f54f8090 100644
--- a/core/control-panel/impl/src/services/deploy.rs
+++ b/core/control-panel/impl/src/services/deploy.rs
@@ -103,6 +103,7 @@ impl DeployService {
quorum: Some(1),
fallback_controller: Some(NNS_ROOT_CANISTER_ID),
accounts: None,
+ assets: None,
}))
.map_err(|err| DeployError::Failed {
reason: err.to_string(),
diff --git a/core/station/api/spec.did b/core/station/api/spec.did
index a2fbdbd4f..ad4c9c2d4 100644
--- a/core/station/api/spec.did
+++ b/core/station/api/spec.did
@@ -59,6 +59,9 @@ type RequestSpecifier = variant {
EditUserGroup : ResourceIds;
RemoveUserGroup : ResourceIds;
ManageSystemInfo;
+ AddAsset;
+ EditAsset : ResourceIds;
+ RemoveAsset : ResourceIds;
};
// A record type that can be used to represent a percentage of users that are required to approve a rule.
@@ -322,6 +325,10 @@ type RequestApproval = record {
type TransferOperationInput = record {
// The account id to use for the transaction.
from_account_id : UUID;
+ // The asset id to transfer.
+ from_asset_id : UUID;
+ // The standard to use for the transfer.
+ with_standard : text;
// The amount to transfer.
amount : nat;
// The destination address of the transaction (e.g. "1BvBMSE...").
@@ -342,6 +349,8 @@ type TransferOperationInput = record {
type TransferOperation = record {
// The account to use for the transaction.
from_account : opt Account;
+ // The asset to use for the transaction.
+ from_asset : Asset;
// The network to use for the transaction.
network : Network;
// The input to the request to transfer funds.
@@ -352,12 +361,27 @@ type TransferOperation = record {
fee : opt nat;
};
+// Mutate the list of assets.
+type ChangeAssets = variant {
+ // Replace all current assets with the specified list.
+ ReplaceWith : record {
+ assets : vec UUID;
+ };
+ // Change the list of assets by adding and removing assets.
+ Change : record {
+ add_assets : vec UUID;
+ remove_assets : vec UUID;
+ };
+};
+
// Input type for editing an account through a request.
type EditAccountOperationInput = record {
// The account id that will be edited.
account_id : UUID;
// A friendly name for the account (e.g. "My Account").
name : opt text;
+ // Mutate the list of assets.
+ change_assets : opt ChangeAssets;
// Who can read the account information.
read_permission : opt Allow;
// Who can request configuration changes to the account.
@@ -379,10 +403,8 @@ type EditAccountOperation = record {
type AddAccountOperationInput = record {
// A friendly name for the account (e.g. "My Account").
name : text;
- // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
- blockchain : text;
- // The asset standard for this account (e.g. `native`, `erc20`, etc.).
- standard : text;
+ // The assets to add to the account.
+ assets : vec UUID;
// Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`).
metadata : vec AccountMetadata;
// Who can read the account information.
@@ -417,6 +439,8 @@ type AddAddressBookEntryOperationInput = record {
address_owner : text;
// The actual address.
address : text;
+ // The format of the address, eg. icp_account_identifier
+ address_format : text;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
blockchain : text;
// Metadata associated with the address book entry (e.g. `{"kyc": "true"}`).
@@ -922,6 +946,12 @@ type RequestOperation = variant {
RemoveRequestPolicy : RemoveRequestPolicyOperation;
// An operation for managing system info.
ManageSystemInfo : ManageSystemInfoOperation;
+ // An operation for adding a new asset.
+ AddAsset : AddAssetOperation;
+ // An operation for editing an existing asset.
+ EditAsset : EditAssetOperation;
+ // An operation for removing an existing asset.
+ RemoveAsset : RemoveAssetOperation;
};
type RequestOperationInput = variant {
@@ -971,6 +1001,12 @@ type RequestOperationInput = variant {
RemoveRequestPolicy : RemoveRequestPolicyOperationInput;
// An operation for managing system info.
ManageSystemInfo : ManageSystemInfoOperationInput;
+ // An operation for adding a new asset.
+ AddAsset : AddAssetOperationInput;
+ // An operation for editing an existing asset.
+ EditAsset : EditAssetOperationInput;
+ // An operation for removing an existing asset.
+ RemoveAsset : RemoveAssetOperationInput;
};
type RequestOperationType = variant {
@@ -1020,6 +1056,12 @@ type RequestOperationType = variant {
RemoveRequestPolicy;
// And operation for managing system info.
ManageSystemInfo;
+ // An operation for adding a new asset.
+ AddAsset;
+ // An operation for editing an existing asset.
+ EditAsset;
+ // An operation for removing an existing asset.
+ RemoveAsset;
};
// The schedule for executing a transaction of a given transfer.
@@ -1170,6 +1212,12 @@ type ListRequestsOperationType = variant {
ManageSystemInfo;
// An operation for setting disaster recovery config.
SetDisasterRecovery;
+ // An operation for adding an asset.
+ AddAsset;
+ // An operation for editing an asset.
+ EditAsset;
+ // An operation for removing an asset.
+ RemoveAsset;
};
// The direction to use for sorting.
@@ -1549,21 +1597,15 @@ type AccountCallerPrivileges = record {
type Account = record {
// The internal account id.
id : UUID;
- // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
- blockchain : text;
- // The asset symbol, e.g. "ICP" or "BTC".
- symbol : AssetSymbol;
- // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
- // with spaces replaced with underscores.
- standard : text;
- // The address of the account (e.g. "0x1234").
- address : text;
- // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
- decimals : nat32;
+
+ // The list of assets supported by this account.
+ assets : vec AccountAsset;
+
+ // The list of addresses associated with the account.
+ addresses : vec AccountAddress;
+
// A friendly name for the account.
name : text;
- // Account balance when available.
- balance : opt AccountBalanceInfo;
// Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`).
metadata : vec AccountMetadata;
// The transfer approval policy for the account.
@@ -1578,6 +1620,25 @@ type Account = record {
last_modification_timestamp : TimestampRFC3339;
};
+// The seed used to derive the addresses of the account.
+type AccountSeed = blob;
+
+// Record type to describe an address of an account.
+type AccountAddress = record {
+ // The address.
+ address : text;
+ // The format of the address, eg. icp_account_identifier.
+ format : text;
+};
+
+// Record type to describe an asset of an account.
+type AccountAsset = record {
+ // The asset id.
+ asset_id : UUID;
+ // The balance of the asset.
+ balance : opt AccountBalance;
+};
+
// Input type for getting a account.
type GetAccountInput = record {
// The account id to retrieve.
@@ -1600,12 +1661,19 @@ type GetAccountResult = variant {
type AccountBalance = record {
// The account id.
account_id : UUID;
+ // The asset id.
+ asset_id : UUID;
// The balance of the account.
balance : nat;
// The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
decimals : nat32;
// The time at which the balance was last updated.
last_update_timestamp : TimestampRFC3339;
+ // The state of balance query:
+ // - `fresh`: The balance was recently updated and is considered fresh.
+ // - `stale`: The balance may be out of date.
+ // - `stale_refreshing`: The balance may be out of date but it is being refreshed in the background.
+ query_state : text;
};
// Input type for getting a account balance.
@@ -1619,7 +1687,7 @@ type FetchAccountBalancesResult = variant {
// The result data for a successful execution.
Ok : record {
// The account balance that was retrieved.
- balances : vec AccountBalance;
+ balances : vec opt AccountBalance;
};
// The error that occurred (e.g. the user does not have the necessary permissions).
Err : Error;
@@ -1652,6 +1720,8 @@ type AddressBookEntry = record {
address_owner : text;
// The actual address.
address : text;
+ // The address format (e.g. "icp_account_identifier").
+ address_format : text;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
blockchain : text;
// Metadata associated with the address book entry (e.g. `{"kyc": "true"}`).
@@ -1691,6 +1761,8 @@ type ListAddressBookEntriesInput = record {
blockchain : opt text;
// The labels to search for, if provided only address book entries with the given labels will be returned.
labels : opt vec text;
+ // The address formats to search for.
+ address_formats : opt vec text;
// The pagination parameters.
paginate : opt PaginationInput;
};
@@ -1723,19 +1795,41 @@ type AssetMetadata = record {
// A record type that can be used to represent an asset in the station.
type Asset = record {
+ // The internal asset id.
+ id : UUID;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
blockchain : text;
// The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
// with spaces replaced with underscores.
- standard : text;
+ standards : vec text;
// The asset symbol, e.g. "ICP" or "BTC".
symbol : AssetSymbol;
// The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.)
name : text;
- // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`),
- // also, in the case of non-native assets, it can contain other required
- // information (e.g. `{"address": "0x1234"}`).
+ // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`).
metadata : vec AssetMetadata;
+ // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
+ decimals : nat32;
+};
+
+// Describes a standard suported by a blockchain.
+type StandardData = record {
+ // The standard name.
+ standard : text;
+ // Required metadata fields for the standard (e.g. `["ledger_canister_id"]`).
+ required_metadata_fields : vec text;
+ // Supported operations for the standard (e.g. `["transfer", "list_transfers", "balance"]`).
+ supported_operations : vec text;
+ // Supported address formats of the standard.
+ supported_address_formats : vec text;
+};
+
+// Describes a blockchain and its standards supported by the station.
+type SupportedBlockchain = record {
+ // The blockchain name.
+ blockchain : text;
+ // The supported standards for the blockchain.
+ supported_standards : vec StandardData;
};
// A record type that is used to show the current capabilities of the station.
@@ -1746,6 +1840,8 @@ type Capabilities = record {
version : text;
// The list of supported assets.
supported_assets : vec Asset;
+ // The list of supported blockchains and standards.
+ supported_blockchains : vec SupportedBlockchain;
};
// Result type for getting the current config.
@@ -2014,6 +2110,7 @@ type Resource = variant {
System : SystemResourceAction;
User : UserResourceAction;
UserGroup : ResourceAction;
+ Asset : ResourceAction;
};
// A record type that can be used to represent the caller privileges for a given permission.
@@ -2174,6 +2271,122 @@ type ListRequestPoliciesResult = variant {
Err : Error;
};
+type AddAssetOperation = record {
+ // The result of adding an asset.
+ asset : opt Asset;
+ // The input to the request to add an asset.
+ input : AddAssetOperationInput;
+};
+
+// The input type for adding an asset.
+type AddAssetOperationInput = record {
+ // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
+ blockchain : text;
+ // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
+ // with spaces replaced with underscores.
+ standards : vec text;
+ // The asset symbol, e.g. "ICP" or "BTC".
+ symbol : AssetSymbol;
+ // The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.)
+ name : text;
+ // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`).
+ metadata : vec AssetMetadata;
+ // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.).
+ decimals : nat32;
+};
+
+type EditAssetOperation = record {
+ // The input to the request to edit an asset.
+ input : EditAssetOperationInput;
+};
+
+// The input type for editing an asset.
+type EditAssetOperationInput = record {
+ // The asset id to edit.
+ asset_id : UUID;
+ // The name of the asset.
+ name : opt text;
+ // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
+ blockchain : opt text;
+ // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string
+ // with spaces replaced with underscores.
+ standards : opt vec text;
+ // The asset symbol, e.g. "ICP" or "BTC".
+ symbol : opt AssetSymbol;
+ // The metadata to change.
+ change_metadata : opt ChangeMetadata;
+};
+
+// Type for instructions to update the address book entry's metadata.
+type ChangeMetadata = variant {
+ // Replace all existing metadata by the specified metadata.
+ ReplaceAllBy : vec AssetMetadata;
+ // Override values of existing metadata with the specified keys
+ // and add new metadata if no metadata can be found with the specified keys.
+ OverrideSpecifiedBy : vec AssetMetadata;
+ // Remove metadata with the specified keys.
+ RemoveKeys : vec text;
+};
+
+type RemoveAssetOperation = record {
+ // The input to the request to remove an asset.
+ input : RemoveAssetOperationInput;
+};
+
+// The input type for removing an asset.
+type RemoveAssetOperationInput = record {
+ // The asset id to remove.
+ asset_id : UUID;
+};
+
+// The input type for listing assets.
+type ListAssetsInput = record {
+ // The pagination parameters.
+ paginate : opt PaginationInput;
+};
+
+// The result type for listing assets.
+type ListAssetsResult = variant {
+ // The result data for a successful execution.
+ Ok : record {
+ // The list of assets.
+ assets : vec Asset;
+ // The offset to use for the next page.
+ next_offset : opt nat64;
+ // The total number of assets.
+ total : nat64;
+ // The caller privileges for the assets.
+ privileges : vec AssetCallerPrivileges;
+ };
+ // The error that occurred (e.g. the user does not have the necessary permissions).
+ Err : Error;
+};
+
+// The input type for getting an asset.
+type GetAssetInput = record {
+ // The asset id to retrieve.
+ asset_id : UUID;
+};
+
+// The result type for getting an asset.
+type GetAssetResult = variant {
+ // The result data for a successful execution.
+ Ok : record {
+ // The asset that was retrieved.
+ asset : Asset;
+ // The caller privileges for the asset.
+ privileges : AssetCallerPrivileges;
+ };
+ // The error that occurred (e.g. the user does not have the necessary permissions).
+ Err : Error;
+};
+
+type AssetCallerPrivileges = record {
+ id : UUID;
+ can_edit : bool;
+ can_delete : bool;
+};
+
// The top level privileges that the user has when making calls to the canister.
type UserPrivilege = variant {
Capabilities;
@@ -2195,6 +2408,8 @@ type UserPrivilege = variant {
CreateExternalCanister;
ListExternalCanisters;
CallAnyExternalCanister;
+ ListAssets;
+ AddAsset;
};
type MeResult = variant {
@@ -2230,13 +2445,31 @@ type InitAccountInput = record {
// A friendly name for the account (e.g. "My Account").
name : text;
// The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
- blockchain : text;
+ seed : AccountSeed;
// The asset standard for this account (e.g. `native`, `erc20`, etc.).
- standard : text;
+ assets : vec UUID;
// Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`).
metadata : vec AccountMetadata;
};
+// The initial assets to create when initializing the canister for the first time, e.g., after disaster recovery.
+type InitAssetInput = record {
+ // The UUID of the asset, if not provided a new UUID will be generated.
+ id : UUID;
+ // The name of the asset.
+ name : text;
+ // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.)
+ blockchain : text;
+ // The standards this asset supports.
+ standards : vec text;
+ // The asset symbol, e.g. "ICP" or "BTC".
+ symbol : text;
+ // The number of decimals used to format the asset balance.
+ decimals : nat32;
+ // Metadata associated with the asset.
+ metadata : vec AssetMetadata;
+};
+
// The init configuration for the canister.
//
// Only used when installing the canister for the first time.
@@ -2253,6 +2486,8 @@ type SystemInit = record {
fallback_controller : opt principal;
// Optional initial accounts to create.
accounts : opt vec InitAccountInput;
+ // Optional initial assets to create.
+ assets : opt vec InitAssetInput;
};
// The upgrade configuration for the canister.
@@ -2766,4 +3001,8 @@ service : (opt SystemInstall) -> {
http_request : (HttpRequest) -> (HttpResponse) query;
// Internal endpoint used by the upgrader canister to notify the station about a failed station upgrade request.
notify_failed_station_upgrade : (NotifyFailedStationUpgradeInput) -> (NotifyFailedStationUpgradeResult);
+ // Get an asset by id.
+ get_asset : (input : GetAssetInput) -> (GetAssetResult) query;
+ // List all assets that the caller has access to.
+ list_assets : (input : ListAssetsInput) -> (ListAssetsResult) query;
};
diff --git a/core/station/api/src/account.rs b/core/station/api/src/account.rs
index b5a4e67d7..d55c318ce 100644
--- a/core/station/api/src/account.rs
+++ b/core/station/api/src/account.rs
@@ -14,22 +14,44 @@ pub struct AccountCallerPrivilegesDTO {
pub struct AccountDTO {
pub id: UuidDTO,
pub name: String,
- pub address: String,
- pub blockchain: String,
- pub standard: String,
- pub symbol: String,
- pub decimals: u32,
- pub balance: Option,
+ pub assets: Vec,
+ pub addresses: Vec,
pub metadata: Vec,
pub transfer_request_policy: Option,
pub configs_request_policy: Option,
pub last_modification_timestamp: String,
}
+pub type AccountSeedDTO = [u8; 16];
+
+#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
+pub struct AccountAssetDTO {
+ pub asset_id: UuidDTO,
+ pub balance: Option,
+}
+
+#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
+pub struct AccountAddressDTO {
+ pub address: String,
+ pub format: String,
+}
+
+#[derive(CandidType, serde::Serialize, Deserialize, Clone, Debug)]
+pub enum ChangeAssets {
+ ReplaceWith {
+ assets: Vec,
+ },
+ Change {
+ add_assets: Vec,
+ remove_assets: Vec,
+ },
+}
+
#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
pub struct EditAccountOperationInput {
pub account_id: UuidDTO,
pub name: Option,
+ pub change_assets: Option,
pub read_permission: Option,
pub configs_permission: Option,
pub transfer_permission: Option,
@@ -45,8 +67,7 @@ pub struct EditAccountOperationDTO {
#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
pub struct AddAccountOperationInput {
pub name: String,
- pub blockchain: String,
- pub standard: String,
+ pub assets: Vec,
pub metadata: Vec,
pub read_permission: AllowDTO,
pub configs_permission: AllowDTO,
@@ -80,9 +101,11 @@ pub struct FetchAccountBalancesInput {
#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
pub struct AccountBalanceDTO {
pub account_id: String,
+ pub asset_id: String,
pub balance: candid::Nat,
pub decimals: u32,
pub last_update_timestamp: String,
+ pub query_state: String,
}
#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
@@ -94,7 +117,7 @@ pub struct AccountBalanceInfoDTO {
#[derive(CandidType, serde::Serialize, Deserialize, Debug, Clone)]
pub struct FetchAccountBalancesResponse {
- pub balances: Vec,
+ pub balances: Vec | |