Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: build icrc1_transfer consent message if target does not implement ICRC-21 #360

Merged
merged 37 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
eb8406b
feat: build icrc1_transfer consent message if target does not impleme…
peterpeterparker Dec 21, 2024
1bfce8c
feat: i18n
peterpeterparker Dec 21, 2024
7e3a88c
feat: title and amount
peterpeterparker Dec 21, 2024
509a1bf
feat: with subaccount
peterpeterparker Dec 21, 2024
b83570d
feat: with subaccount
peterpeterparker Dec 21, 2024
3866a64
feat: to account
peterpeterparker Dec 21, 2024
335733c
feat: fee
peterpeterparker Dec 21, 2024
772a5b8
feat: memo
peterpeterparker Dec 21, 2024
083c414
chore: lint
peterpeterparker Dec 21, 2024
8a8c972
feat: format utils
peterpeterparker Dec 23, 2024
6c1543f
feat: mapper
peterpeterparker Dec 23, 2024
a587748
feat: min two decimals for readability
peterpeterparker Dec 23, 2024
e338fa3
feat: format amount
peterpeterparker Dec 23, 2024
ff5e86d
feat: fee format and fallback
peterpeterparker Dec 23, 2024
c81046e
feat: token symbol
peterpeterparker Dec 23, 2024
afd590c
refactor: extract consent message
peterpeterparker Dec 23, 2024
ceeb02a
feat: use builders
peterpeterparker Dec 23, 2024
4991d3d
chore: rm test
peterpeterparker Dec 23, 2024
828fceb
chore: merge main
peterpeterparker Dec 23, 2024
e8595ba
chore: todo
peterpeterparker Dec 23, 2024
77fdc04
build: bump ic next
peterpeterparker Dec 23, 2024
a5998cf
feat: builder return object for consent message
peterpeterparker Dec 23, 2024
5fbd709
feat: types
peterpeterparker Dec 23, 2024
8ba0975
Merge branch 'main' into feat/build_icrc1_transfer_consent_message
peterpeterparker Dec 23, 2024
a4aa1b5
chore: merge main
peterpeterparker Dec 23, 2024
4eceb12
docs: builder
peterpeterparker Dec 23, 2024
c666d20
test: ledger in signer api
peterpeterparker Dec 23, 2024
0eaa766
test: bubble error
peterpeterparker Dec 23, 2024
fa87ee4
chore: merge main
peterpeterparker Dec 23, 2024
b017e79
Merge branch 'main' into feat/build_icrc1_transfer_consent_message
peterpeterparker Dec 24, 2024
95b714d
refactor: method instead of function and rename fallback
peterpeterparker Dec 24, 2024
0b50366
test: error with builder
peterpeterparker Dec 24, 2024
9ce617c
chore: fmt
peterpeterparker Dec 24, 2024
149220e
test: throws return
peterpeterparker Dec 24, 2024
5a92b3f
test: approves
peterpeterparker Dec 24, 2024
b42c3bd
Merge branch 'main' into feat/build_icrc1_transfer_consent_message
peterpeterparker Dec 25, 2024
ff334ec
chore: update todo
peterpeterparker Dec 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/constants/signer.builders.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {buildContentMessageIcrc1Transfer} from '../builders/signer.builders';
import {SignerBuilderFn, SignerBuilderMethods} from '../types/signer-builders';

export const SIGNER_BUILDERS: Record<SignerBuilderMethods, SignerBuilderFn> = {
icrc1_transfer: buildContentMessageIcrc1Transfer
};
79 changes: 77 additions & 2 deletions src/services/signer.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {mapTokenMetadata} from '@dfinity/ledger-icrc';
import {Principal} from '@dfinity/principal';
import {isNullish, notEmptyString} from '@dfinity/utils';
import {SignerApi} from '../api/signer.api';
import {icrc21_consent_message_response} from '../declarations/icrc-21';
import {SIGNER_BUILDERS} from '../constants/signer.builders.constants';
import {icrc21_consent_info, icrc21_consent_message_response} from '../declarations/icrc-21';
import {
notifyErrorActionAborted,
notifyErrorMissingPrompt,
Expand Down Expand Up @@ -53,7 +55,7 @@ export class SignerService {
prompt({origin, status: 'loading'});

try {
const response = await this.callConsentMessage({
const response = await this.loadConsentMessage({
params,
options: {host, owner}
});
Expand All @@ -73,6 +75,11 @@ export class SignerService {

const {Ok: consentInfo} = response;

// TODO: change consent message prompt payload
// {
// {Ok: consentInfo} | {Warn: {consentInfo?: string, method, arg, canisterId, owner}}
// }

const {result} = await this.promptConsentMessage({consentInfo, prompt, origin});

if (result === 'rejected') {
Expand Down Expand Up @@ -222,4 +229,72 @@ export class SignerService {

return await promise;
}

/**
* If the ICRC-21 call to fetch the consent message fails, it might be due to the fact
* that the targeted canister does not implement the ICRC-21 specification.
*
* To address the potential lack of support for the most common types of calls for ledgers,
* namely transfer and approve, we use custom builders. Those builders construct
* messages similar to those that would be implemented by the canisters.
*
* @param {Object} params - The parameters for loading the consent message.
* @param {Omit<IcrcCallCanisterRequestParams, 'sender'>} params.params - The ICRC call canister parameters minus the sender.
* @param {SignerOptions} params.options - The signer options - host and owner.
* @returns {Promise<icrc21_consent_message_response>} - The consent message response.
* @throws The potential original error from the ICRC-21 call. The errors related to
* the custom builder is ignored.
**/
private async loadConsentMessage(params: {
params: Omit<IcrcCallCanisterRequestParams, 'sender'>;
options: SignerOptions;
}): Promise<icrc21_consent_message_response> {
try {
return await this.callConsentMessage(params);
} catch (err: unknown) {
const fallbackMessage = await this.tryBuildConsentMessageOnError(params);

if ('Ok' in fallbackMessage) {
return fallbackMessage;
}

throw err;
}
}

private async tryBuildConsentMessageOnError({
params: {method, arg, canisterId},
options: {owner, host}
}: {
params: Omit<IcrcCallCanisterRequestParams, 'sender'>;
options: SignerOptions;
}): Promise<{NoFallback: null} | {Ok: icrc21_consent_info} | {Err: unknown}> {
const fn = SIGNER_BUILDERS[method];

if (isNullish(fn)) {
return {NoFallback: null};
}

try {
const tokenResponse = await this.#signerApi.ledgerMetadata({
params: {canisterId},
host,
owner
});

const token = mapTokenMetadata(tokenResponse);

if (isNullish(token)) {
return {Err: new Error('Incomplete token metadata.')};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any i18n reference needed here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, I would say. This particular error also not handled in any specific way since it's the original error—the lack of ICRC-21 support—which will be bubbled up to the caller.

All error messages are currently provided in English. I'm not sure what criteria would be used for selecting another language. Maybe something to improve globally in the future, in that sense good point!

}

return await fn({
arg: base64ToUint8Array(arg),
token,
owner: owner.getPrincipal()
});
} catch (err: unknown) {
return {Err: err};
}
}
}
Loading
Loading