Skip to content

Commit

Permalink
feat: import tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Nov 28, 2024
1 parent cffba43 commit ae4d006
Show file tree
Hide file tree
Showing 25 changed files with 564 additions and 144 deletions.
2 changes: 1 addition & 1 deletion src/ic_message_frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@
"test": "vitest run"
},
"type": "module",
"version": "2.6.11"
"version": "2.7.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type ProfileInfo = record {
channels : opt vec record { record { principal; nat64 }; ChannelSetting };
image_file : opt record { principal; nat32 };
links : vec Link;
tokens : vec principal;
canister : principal;
ecdh_pub : opt blob;
following : opt vec principal;
Expand Down Expand Up @@ -87,6 +88,7 @@ service : (opt ChainArgs) -> {
update_links : (vec Link) -> (Result);
update_profile : (UpdateProfileInput) -> (Result_2);
update_profile_ecdh_pub : (blob) -> (Result);
update_tokens : (vec principal) -> (Result);
upload_image_token : (UploadImageInput) -> (Result_4);
validate2_admin_add_managers : (vec principal) -> (Result_5);
validate2_admin_remove_managers : (vec principal) -> (Result_5);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface ProfileInfo {
'channels' : [] | [Array<[[Principal, bigint], ChannelSetting]>],
'image_file' : [] | [[Principal, number]],
'links' : Array<Link>,
'tokens' : Array<Principal>,
'canister' : Principal,
'ecdh_pub' : [] | [Uint8Array | number[]],
'following' : [] | [Array<Principal>],
Expand Down Expand Up @@ -114,6 +115,7 @@ export interface _SERVICE {
'update_links' : ActorMethod<[Array<Link>], Result>,
'update_profile' : ActorMethod<[UpdateProfileInput], Result_2>,
'update_profile_ecdh_pub' : ActorMethod<[Uint8Array | number[]], Result>,
'update_tokens' : ActorMethod<[Array<Principal>], Result>,
'upload_image_token' : ActorMethod<[UploadImageInput], Result_4>,
'validate2_admin_add_managers' : ActorMethod<[Array<Principal>], Result_5>,
'validate2_admin_remove_managers' : ActorMethod<[Array<Principal>], Result_5>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const idlFactory = ({ IDL }) => {
),
'image_file' : IDL.Opt(IDL.Tuple(IDL.Principal, IDL.Nat32)),
'links' : IDL.Vec(Link),
'tokens' : IDL.Vec(IDL.Principal),
'canister' : IDL.Principal,
'ecdh_pub' : IDL.Opt(IDL.Vec(IDL.Nat8)),
'following' : IDL.Opt(IDL.Vec(IDL.Principal)),
Expand Down Expand Up @@ -129,6 +130,7 @@ export const idlFactory = ({ IDL }) => {
'update_links' : IDL.Func([IDL.Vec(Link)], [Result], []),
'update_profile' : IDL.Func([UpdateProfileInput], [Result_2], []),
'update_profile_ecdh_pub' : IDL.Func([IDL.Vec(IDL.Nat8)], [Result], []),
'update_tokens' : IDL.Func([IDL.Vec(IDL.Principal)], [Result], []),
'upload_image_token' : IDL.Func([UploadImageInput], [Result_4], []),
'validate2_admin_add_managers' : IDL.Func(
[IDL.Vec(IDL.Principal)],
Expand Down
5 changes: 5 additions & 0 deletions src/ic_message_frontend/src/lib/canisters/messageprofile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ export class ProfileAPI {
const res = await this.actor.update_links(input)
return unwrapResult(res, 'call update_links failed')
}

async update_tokens(input: Principal[]): Promise<null> {
const res = await this.actor.update_tokens(input)
return unwrapResult(res, 'call update_tokens failed')
}
}
43 changes: 42 additions & 1 deletion src/ic_message_frontend/src/lib/canisters/tokenledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,47 @@ export class TokenLedgerAPI {
this.token = token
}

static async fromID(canisterId: Principal): Promise<TokenLedgerAPI> {
const actor = createActor<_SERVICE>({
canisterId,
idlFactory: idlFactory
})
const metadata = await actor.icrc1_metadata()
const token: TokenInfo = {
name: 'Internet Computer',
symbol: 'ICP',
decimals: 8,
fee: 10000n,
one: 100000000n,
logo: '',
canisterId: canisterId.toText()
}

for (const [key, value] of metadata) {
switch (key) {
case 'icrc1:name':
token.name = (value as { 'Text': string }).Text
continue
case 'icrc1:symbol':
token.symbol = (value as { 'Text': string }).Text
continue
case 'icrc1:decimals':
const decimals = (value as { 'Nat': bigint }).Nat
token.decimals = Number(decimals)
token.one = 10n ** decimals
continue
case 'icrc1:fee':
token.fee = (value as { 'Nat': bigint }).Nat
continue
case 'icrc1:logo':
token.logo = (value as { 'Text': string }).Text
continue
}
}

return new TokenLedgerAPI(token)
}

async balance(): Promise<bigint> {
return this.getBalanceOf(agent.id.getPrincipal())
}
Expand Down Expand Up @@ -80,4 +121,4 @@ export class TokenLedgerAPI {
}
}

export const tokenLedgerAPI = new TokenLedgerAPI(PANDAToken)
export const pandaLedgerAPI = new TokenLedgerAPI(PANDAToken)
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<script lang="ts">
import { TokenLedgerAPI } from '$lib/canisters/tokenledger'
import IconCircleSpin from '$lib/components/icons/IconCircleSpin.svelte'
import IconExternalLinkLine from '$lib/components/icons/IconExternalLinkLine.svelte'
import ModalCard from '$lib/components/ui/ModalCard.svelte'
import type { LedgerAPI } from '$lib/types/token'
import { TokenDisplay, type TokenInfo } from '$lib/utils/token'
import { Principal } from '@dfinity/principal'
import { tick, type SvelteComponent } from 'svelte'
// Props
type TokenInfoEx = TokenInfo & { api: LedgerAPI; balance: bigint }
interface Props {
/** Exposes parent props to this component. */
parent: SvelteComponent
onsave: (token: TokenInfoEx) => Promise<void>
}
let { parent, onsave }: Props = $props()
let ledgerId = $state('')
let ledgerErr = $state('')
let token: TokenInfoEx | null = $state(null)
let submitting = $state(false)
async function onFormChange(e: Event) {
e.stopPropagation()
e.preventDefault()
ledgerErr = ''
let id: Principal | null = null
token = null
try {
id = Principal.fromText(ledgerId)
} catch (e) {}
if (!id) {
return
}
try {
submitting = true
await tick()
const api = await TokenLedgerAPI.fromID(id)
const balance = await api.balance()
token = { ...api.token, api, balance }
} catch (e) {
token = null
ledgerErr = 'Token not found'
}
submitting = false
}
async function onclick(e: Event) {
if (token) {
submitting = true
await onsave(token)
parent['onClose'] && parent['onClose']()
}
}
</script>

<ModalCard {parent}>
<div class="!mt-0 text-center text-xl font-bold">Import Token</div>

<form
class="m-auto !mt-4 flex flex-col content-center"
oninput={onFormChange}
>
<div class="">
<p class="">
You can import a new token to wallet by providing its ledger canister
ID.
</p>
<a
type="button"
class="mt-2 flex w-fit flex-row items-center gap-2 text-primary-500"
target="_blank"
href="https://internetcomputer.org/docs/current/developer-docs/daos/nns/using-the-nns-dapp/nns-dapp-importing-tokens"
>
<span class="*:size-5"><IconExternalLinkLine /></span>
<span class="hover:underline">How to find ICRC token ledger</span>
</a>
</div>
<div class="relative mt-4">
<input
class="border-gray/10 input truncate rounded-xl bg-white/20 invalid:input-warning"
type="text"
name="ledgerId"
maxlength="27"
data-1p-ignore
bind:value={ledgerId}
disabled={submitting}
placeholder="_____-_____-_____-_____-cai"
required
/>
</div>
{#if token}
<div class="mt-4 grid grid-cols-[48px_1fr]">
<img class="size-12" alt={token.name} src={token.logo} />
<div class="pl-2">
<div class="flex flex-row justify-between">
<span class="">{token.symbol}</span>
<span class=""
>{new TokenDisplay(token, token.balance).display()}</span
>
</div>
<div class="flex flex-row justify-between text-sm text-surface-500">
<span class="">{token.name}</span>
</div>
</div>
</div>
{:else if ledgerErr}
<div class="mt-4">
<p class="text-error-500">{ledgerErr}</p>
</div>
{/if}
</form>
<footer class="m-auto !mt-6">
<button
class="variant-filled-primary btn w-full"
disabled={submitting || !token}
{onclick}
>
{#if submitting}
<span class=""><IconCircleSpin /></span>
<span>Processing...</span>
{:else}
<span>Import</span>
{/if}
</button>
</footer>
</ModalCard>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts">
import ModalCard from '$lib/components/ui/ModalCard.svelte'
import SendTokenForm from '$lib/components/ui/SendTokenForm.svelte'
import type { LedgerAPI, SendTokenArgs } from '$lib/types/token'
import { type TokenInfo } from '$lib/utils/token'
import type { Principal } from '@dfinity/principal'
import { type SvelteComponent } from 'svelte'
// Props
type TokenInfoEx = TokenInfo & { api: LedgerAPI; balance: bigint }
interface Props {
/** Exposes parent props to this component. */
parent: SvelteComponent
token: TokenInfoEx
principal: Principal
}
let { parent, principal, token }: Props = $props()
async function handleTokenTransfer(args: SendTokenArgs) {
const idx = await token.api.transfer(args.to, args.amount)
token.balance = await token.api.balance()
return idx
}
</script>

<ModalCard {parent}>
<div class="!mt-0 text-center text-xl font-bold">Transfer {token.symbol}</div>
<div class="mx-auto !mt-6 space-y-4">
<SendTokenForm
sendFrom={principal}
availableBalance={token.balance}
{token}
onSubmit={handleTokenTransfer}
/>
</div>
</ModalCard>
Loading

0 comments on commit ae4d006

Please sign in to comment.