Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 21 additions & 7 deletions packages/functions/src/canisters/cmc/cmc.canister.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {schemaFromIdl, schemaToIdl} from '@junobuild/schema/utils';
import {call} from '../../ic-cdk/call.ic-cdk';
import {Canister} from '../_canister';
import {CMC_ID} from '../_constants';
import {type CmcDid, CmcIdl} from '../declarations';
import {type CanisterOptions, CanisterOptionsSchema} from '../schemas';
import {CmcIdl} from '../declarations';
import {type CanisterOptions, CanisterOptionsSchema} from '../schema';
import {
type NotifyTopUpArgs,
type NotifyTopUpResult,
NotifyTopUpArgsSchema,
NotifyTopUpResultSchema
} from './schema';

/**
* Provides a simple interface to interact with the Cycle Minting Canister,
Expand All @@ -27,14 +34,21 @@ export class CMCCanister extends Canister {
* The CMC will then convert the ICP from the given ledger block into cycles and add
* them to the specified canister.
*
* @param {CmcDid.NotifyTopUpArg} args - Arguments containing the ledger block index and the canister ID that should receive the cycles.
* @returns {Promise<CmcDid.NotifyTopUpResult>} The result of the CMC conversion and deposit.
* @param {NotifyTopUpArgs} args - Arguments containing the ledger block index and the canister ID that should receive the cycles.
* @returns {Promise<NotifyTopUpResult>} The result of the CMC conversion and deposit.
*/
notifyTopUp = async ({args}: {args: CmcDid.NotifyTopUpArg}): Promise<CmcDid.NotifyTopUpResult> =>
await call<CmcDid.NotifyTopUpResult>({
notifyTopUp = async ({args}: {args: NotifyTopUpArgs}): Promise<NotifyTopUpResult> => {
const parsed = NotifyTopUpArgsSchema.parse(args);

const idlArgs = schemaToIdl({schema: NotifyTopUpArgsSchema, value: parsed});

const idlResult = await call<NotifyTopUpResult>({
canisterId: this.canisterId,
method: 'notify_top_up',
args: [[CmcIdl.NotifyTopUpArg, args]],
args: [[CmcIdl.NotifyTopUpArg, idlArgs]],
result: CmcIdl.NotifyTopUpResult
});

return schemaFromIdl({schema: NotifyTopUpResultSchema, value: idlResult}) as NotifyTopUpResult;
};
}
1 change: 1 addition & 0 deletions packages/functions/src/canisters/cmc/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {CmcIdl, type CmcDid} from '../declarations';
export {CMCCanister} from './cmc.canister';
export * from './schema';
57 changes: 57 additions & 0 deletions packages/functions/src/canisters/cmc/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type {Principal} from '@icp-sdk/core/principal';
import {j} from '@junobuild/schema';
import * as z from 'zod';

export const NotifyTopUpArgsSchema = j.strictObject({
block_index: j.bigint(),
canister_id: j.principal()
});

export const NotifyErrorSchema = z.union([
z.strictObject({
Refunded: z.strictObject({
block_index: z.bigint().optional(),
reason: z.string()
})
}),
z.strictObject({InvalidTransaction: z.string()}),
z.strictObject({Other: z.strictObject({error_message: z.string(), error_code: z.bigint()})}),
z.strictObject({Processing: z.null()}),
z.strictObject({TransactionTooOld: z.bigint()})
]);

export const NotifyTopUpResultSchema = z.union([
z.strictObject({Ok: z.bigint()}),
z.strictObject({Err: NotifyErrorSchema})
]);

/**
* Arguments for the CMC `notify_top_up` call.
*/
export interface NotifyTopUpArgs {
/** Index of the block on the ICP ledger that contains the payment. */
block_index: bigint;
/** The canister to top up. */
canister_id: Principal;
}

/**
* Errors that can occur during a CMC notify call.
*/
export type NotifyError =
/** The payment was returned to the caller. */
| {Refunded: {block_index?: bigint; reason: string}}
/** The transaction does not satisfy the CMC payment protocol. */
| {InvalidTransaction: string}
/** Other error. */
| {Other: {error_message: string; error_code: bigint}}
/** The same payment is already being processed by a concurrent request. */
| {Processing: null}
/** The payment was too old to be processed. */
| {TransactionTooOld: bigint};

/**
* The result of a CMC `notify_top_up` call.
* Returns the amount of cycles sent to the specified canister on success.
*/
export type NotifyTopUpResult = {Ok: bigint} | {Err: NotifyError};
2 changes: 1 addition & 1 deletion packages/functions/src/canisters/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './schemas';
export * from './schema';
1 change: 1 addition & 0 deletions packages/functions/src/canisters/ledger/icp/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {IcpIndexIdl, IcpLedgerIdl, type IcpIndexDid, type IcpLedgerDid} from '../../declarations';
export {IcpLedgerCanister} from './ledger.canister';
export * from './schema';
35 changes: 24 additions & 11 deletions packages/functions/src/canisters/ledger/icp/ledger.canister.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {schemaFromIdl, schemaToIdl} from '@junobuild/schema/utils';
import {call} from '../../../ic-cdk/call.ic-cdk';
import {Canister} from '../../_canister';
import {ICP_LEDGER_ID} from '../../_constants';
import {type IcpLedgerDid, IcpLedgerIdl} from '../../declarations';
import {type CanisterOptions, CanisterOptionsSchema} from '../../schemas';
import {IcpLedgerIdl} from '../../declarations';
import {type CanisterOptions, CanisterOptionsSchema} from '../../schema';
import {
type TransferArgs,
type TransferResult,
TransferArgsSchema,
TransferResultSchema
} from './schema';

/**
* Provides a simple interface to interact with the ICP Ledger,
Expand All @@ -23,18 +30,24 @@ export class IcpLedgerCanister extends Canister {
* Use this to transfer ICP from one account to another when writing
* Juno Serverless Functions in TypeScript.
*
* @param {IcpLedgerDid.TransferArgs} args - The ledger transfer arguments (amount, destination account, memo, fee, etc.).
* @returns {Promise<IcpLedgerDid.TransferResult>} The result of the ICP transfer.
* @param {TransferArgs} args - The ledger transfer arguments (amount, destination account, memo, fee, etc.).
* @returns {Promise<TransferResult>} The result of the ICP transfer.
*/
transfer = async ({
args
}: {
args: IcpLedgerDid.TransferArgs;
}): Promise<IcpLedgerDid.TransferResult> =>
await call<IcpLedgerDid.TransferResult>({
transfer = async ({args}: {args: TransferArgs}): Promise<TransferResult> => {
const parsed = TransferArgsSchema.parse(args);

const idlArgs = schemaToIdl({
schema: TransferArgsSchema,
value: parsed
});

const idlResult = await call<TransferResult>({
canisterId: this.canisterId,
method: 'transfer',
args: [[IcpLedgerIdl.TransferArgs, args]],
args: [[IcpLedgerIdl.TransferArgs, idlArgs]],
result: IcpLedgerIdl.TransferResult
});

return schemaFromIdl({schema: TransferResultSchema, value: idlResult}) as TransferResult;
};
}
93 changes: 93 additions & 0 deletions packages/functions/src/canisters/ledger/icp/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {j} from '@junobuild/schema';
import * as z from 'zod';

export const AccountIdentifierSchema = j.instanceof(Uint8Array);
export const TokensSchema = j.strictObject({e8s: j.bigint()});
export const TimeStampSchema = j.strictObject({timestamp_nanos: j.bigint()});
export const SubAccountSchema = j.instanceof(Uint8Array);

export const TransferArgsSchema = j.strictObject({
to: AccountIdentifierSchema,
fee: TokensSchema,
memo: j.bigint(),
from_subaccount: SubAccountSchema.optional(),
created_at_time: TimeStampSchema.optional(),
amount: TokensSchema
});

export const TransferErrorSchema = z.union([
z.strictObject({TxTooOld: z.strictObject({allowed_window_nanos: z.bigint()})}),
z.strictObject({BadFee: z.strictObject({expected_fee: z.strictObject({e8s: z.bigint()})})}),
z.strictObject({TxDuplicate: z.strictObject({duplicate_of: z.bigint()})}),
z.strictObject({TxCreatedInFuture: z.null()}),
z.strictObject({InsufficientFunds: z.strictObject({balance: z.strictObject({e8s: z.bigint()})})})
]);

export const TransferResultSchema = z.union([
z.strictObject({Ok: z.bigint()}),
z.strictObject({Err: TransferErrorSchema})
]);

/**
* The destination account identifier.
* A 32-byte array where the first 4 bytes are a CRC32 checksum of the last 28 bytes.
*/
export type AccountIdentifier = Uint8Array;

/**
* Amount of tokens, measured in 10^-8 of a token.
*/
export interface Tokens {
e8s: bigint;
}

/**
* A point in time, represented as nanoseconds since the Unix epoch.
*/
export interface TimeStamp {
timestamp_nanos: bigint;
}

/**
* Subaccount is an arbitrary 32-byte array used to compute the source address.
*/
export type SubAccount = Uint8Array;

/**
* Arguments for the ICP Ledger `transfer` call.
*/
export interface TransferArgs {
/** The destination account identifier. */
to: AccountIdentifier;
/** The transaction fee. Must be 10000 e8s. */
fee: Tokens;
/** An arbitrary number associated with the transaction for correlation. */
memo: bigint;
/** The subaccount to transfer from. Uses the default subaccount if not provided. */
from_subaccount?: SubAccount;
/** The time at which the caller created this request. Uses current IC time if not provided. */
created_at_time?: TimeStamp;
/** The amount to transfer to the destination address. */
amount: Tokens;
}

/**
* Errors that can occur during an ICP Ledger transfer.
*/
export type TransferError =
/** The request is too old. The ledger only accepts requests created within a 24-hour window. */
| {TxTooOld: {allowed_window_nanos: bigint}}
/** The fee specified in the transfer request does not match the expected fee. */
| {BadFee: {expected_fee: Tokens}}
/** The ledger has already executed this request. */
| {TxDuplicate: {duplicate_of: bigint}}
/** The specified `created_at_time` is too far in the future. */
| {TxCreatedInFuture: null}
/** The caller's account does not have enough funds. */
| {InsufficientFunds: {balance: Tokens}};

/**
* The result of an ICP Ledger `transfer` call.
* Returns the block index of the transaction on success.
*/
export type TransferResult = {Ok: bigint} | {Err: TransferError};
2 changes: 1 addition & 1 deletion packages/functions/src/canisters/ledger/icrc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export {
type IcrcLedgerDid
} from '../../declarations';
export {IcrcLedgerCanister} from './ledger.canister';
export * from './schemas';
export * from './schema';
79 changes: 50 additions & 29 deletions packages/functions/src/canisters/ledger/icrc/ledger.canister.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import {schemaFromIdl, schemaToIdl} from '@junobuild/schema/utils';
import {call} from '../../../ic-cdk/call.ic-cdk';
import {Canister} from '../../_canister';
import {type IcrcLedgerDid, IcrcLedgerIdl} from '../../declarations';
import {type IcrcCanisterOptions, IcrcCanisterOptionsSchema} from './schemas';
import {IcrcLedgerIdl} from '../../declarations';
import {
AccountSchema,
IcrcCanisterOptionsSchema,
TransferArgsSchema,
TransferFromArgsSchema,
TransferFromResultSchema,
TransferResultSchema,
type Account,
type IcrcCanisterOptions,
type TransferArgs,
type TransferFromArgs,
type TransferFromResult,
type TransferResult
} from './schema';

/**
* Provides a simple interface to interact with an ICRC Ledger,
Expand All @@ -19,60 +33,67 @@ export class IcrcLedgerCanister extends Canister {
/**
* Returns the balance of an ICRC account.
*
* @param {IcrcLedgerDid.Account} account - The account to query.
* @returns {Promise<IcrcLedgerDid.Tokens>} The token balance for the account.
* @param {Account} account - The account to query.
* @returns {Promise<bigint>} The token balance for the account.
*/
icrc1BalanceOf = async ({
account
}: {
account: IcrcLedgerDid.Account;
}): Promise<IcrcLedgerDid.Tokens> =>
await call<IcrcLedgerDid.Tokens>({
icrc1BalanceOf = async ({account}: {account: Account}): Promise<bigint> => {
const parsed = AccountSchema.parse(account);
const idlArgs = schemaToIdl({schema: AccountSchema, value: parsed});

return await call<bigint>({
canisterId: this.canisterId,
method: 'icrc1_balance_of',
args: [[IcrcLedgerIdl.Account, account]],
args: [[IcrcLedgerIdl.Account, idlArgs]],
result: IcrcLedgerIdl.Tokens
});
};

/**
* Transfers tokens using the ICRC-1 `icrc1_transfer` method.
*
* Use this to send tokens from the caller's account to another account
* when writing Juno Serverless Functions in TypeScript.
*
* @param {IcrcLedgerDid.TransferArg} args - Transfer arguments (amount, fee, to, memo, created_at_time, etc.).
* @returns {Promise<IcrcLedgerDid.TransferResult>} The result of the transfer.
* @param {TransferArgs} args - Transfer arguments (amount, fee, to, memo, created_at_time, etc.).
* @returns {Promise<TransferResult>} The result of the transfer.
*/
icrc1Transfer = async ({
args
}: {
args: IcrcLedgerDid.TransferArg;
}): Promise<IcrcLedgerDid.TransferResult> =>
await call<IcrcLedgerDid.TransferResult>({
icrc1Transfer = async ({args}: {args: TransferArgs}): Promise<TransferResult> => {
const parsed = TransferArgsSchema.parse(args);
const idlArgs = schemaToIdl({schema: TransferArgsSchema, value: parsed});

const idlResult = await call<TransferResult>({
canisterId: this.canisterId,
method: 'icrc1_transfer',
args: [[IcrcLedgerIdl.TransferArg, args]],
args: [[IcrcLedgerIdl.TransferArg, idlArgs]],
result: IcrcLedgerIdl.TransferResult
});

return schemaFromIdl({schema: TransferResultSchema, value: idlResult}) as TransferResult;
};

/**
* Transfers tokens using the ICRC-2 `icrc2_transfer_from` method.
*
* Allows transferring tokens from another user's account when an approval
* has previously been granted via `icrc2_approve`.
*
* @param {IcrcLedgerDid.TransferFromArgs} args - Transfer-from arguments (amount, from_subaccount, spender, etc.).
* @returns {Promise<IcrcLedgerDid.TransferFromResult>} The result of the transfer-from operation.
* @param {TransferFromArgs} args - Transfer-from arguments (amount, from_subaccount, spender, etc.).
* @returns {Promise<TransferFromResult>} The result of the transfer-from operation.
*/
icrc2TransferFrom = async ({
args
}: {
args: IcrcLedgerDid.TransferFromArgs;
}): Promise<IcrcLedgerDid.TransferFromResult> =>
await call<IcrcLedgerDid.TransferFromResult>({
icrc2TransferFrom = async ({args}: {args: TransferFromArgs}): Promise<TransferFromResult> => {
const parsed = TransferFromArgsSchema.parse(args);
const idlArgs = schemaToIdl({schema: TransferFromArgsSchema, value: parsed});

const idlResult = await call<TransferFromResult>({
canisterId: this.canisterId,
method: 'icrc2_transfer_from',
args: [[IcrcLedgerIdl.TransferFromArgs, args]],
args: [[IcrcLedgerIdl.TransferFromArgs, idlArgs]],
result: IcrcLedgerIdl.TransferFromResult
});

return schemaFromIdl({
schema: TransferFromResultSchema,
value: idlResult
}) as TransferFromResult;
};
}
Loading
Loading