Skip to content

feat: Add ICRC-21 flow#77

Open
ielashi wants to merge 17 commits intomainfrom
ielashi/icrc21
Open

feat: Add ICRC-21 flow#77
ielashi wants to merge 17 commits intomainfrom
ielashi/icrc21

Conversation

@ielashi
Copy link
Copy Markdown
Collaborator

@ielashi ielashi commented Mar 29, 2026

  • Implements a Icrc21Agent for signing using the ICRC-21 standard
  • Adds a test canister for verifying the implementation of the agent. Tests are possible through mocking the LedgerIdentity.
  • A canister exists in production that showcases the ICRC-21 flow. It can be tested using the following command:
just run icrc21 call --canister-id xxydu-fqaaa-aaaam-ad2ka-cai --method swap --arg $(didc encode '("ICP", "ckBTC", 1000000000 : nat64)')

@ielashi ielashi changed the title Ielashi/icrc21 feat: Add ICRC-21 flow Apr 8, 2026
@ielashi ielashi marked this pull request as ready for review April 8, 2026 14:37
@ielashi ielashi requested a review from a team as a code owner April 8, 2026 14:37
Copy link
Copy Markdown
Contributor

@mducroux mducroux left a comment

Choose a reason for hiding this comment

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

Thanks for adding this flow @ielashi! I run into an issue when running just run icrc21 call --canister-id xxydu-fqaaa-aaaam-ad2ka-cai --method swap --arg $(didc encode '("ICP", "ckBTC", 1000000000 : nat64)') twice and rejecting the transaction on the Ledger the first time:

Error: A ledger error happened during signature:
Code: 27012
Message: "Data is invalid : "

.addCommand(
new Command("call")
.description(
'Request a consent message for a canister call\n\nExample: ic-hardware-wallet icrc21 call --canister-id xxydu-fqaaa-aaaam-ad2ka-cai --method swap --arg $(didc encode \'("ICP", "ckBTC", 1000000000 : nat64)\')'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ic-hardware-wallet -> should be just run or something else?

.addOption(
new Option("--network <network>", "The IC network to talk to.")
.default("https://ic0.app")
.default("https://icp0.io")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

understanding: why did this change?

tryParseHexString
)
.action(async ({ canisterId, method, arg }) =>
run(async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: to be consistent with the other commands, consider moving this into its own function

}
}

export function tryParseHexString(value: string): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: I think the name is slightly misleading as we are not parsing but validating. What about: assertHexString?

* Uses an anonymous agent to fetch consent messages from canisters, then delegates
* the actual call signing to the provided identity via BLS signatures.
*/
export class Icrc21Agent implements Agent {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you need Icrc21Agent to implement Agent? Implementing Agent with three throwing stubs seems misleading.

);

// Reset the ICRC-21 flag after signing
this._icrc21Flag = false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider putting this in a finally statement to make sure it is executed even if signing with BLS fails

let signature: Signature;

if (this._icrc21Flag) {
// Use BLS signing for ICRC-21 transactions
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is that accurate? I read it as ask the ledger wallet to produce a BLS signature but it is not the case right? In which case the name signBls is probably misleading.

}

const consentRequest = consentMessageSubmitResponse.requestDetails;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: consider adding:
if (!consentRequest) { throw new Error("Missing request details from consent message call"); }

const result = await this.signingAgent.call(canisterId, {
methodName: fields.methodName,
arg: fields.arg,
effectiveCanisterId: canisterId,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

understanding: why not: fields.effectiveCanisterId?

// Flag the identity so transformRequest will use BLS signing
this.identity.flagUpcomingIcrc21(consentRequestHex, certificateHex);

// Submit the actual canister call
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: // Send the canister call via the signing agent which triggers ICRC-21 approval on the Ledger

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants