Skip to content

Commit a8def88

Browse files
committed
feat: top-up PANDA from OISY
1 parent a40b00a commit a8def88

File tree

7 files changed

+323
-27
lines changed

7 files changed

+323
-27
lines changed

pnpm-lock.yaml

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ic_message_frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"@dfinity/agent": "^2.1.3",
44
"@dfinity/auth-client": "^2.1.3",
55
"@dfinity/candid": "^2.1.3",
6+
"@dfinity/oisy-wallet-signer": "^0.0.3",
67
"@dfinity/principal": "^2.1.3",
78
"@dfinity/utils": "^2.7.1",
89
"@ldclabs/cose-ts": "^1.3.2",
@@ -82,5 +83,5 @@
8283
"test": "vitest run"
8384
},
8485
"type": "module",
85-
"version": "2.8.3"
86+
"version": "2.8.4"
8687
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<script lang="ts">
2+
import IconCircleSpin from '$lib/components/icons/IconCircleSpin.svelte'
3+
import Loading from '$lib/components/ui/Loading.svelte'
4+
import ModalCard from '$lib/components/ui/ModalCard.svelte'
5+
import { oisyWallet } from '$lib/stores/oisy'
6+
import { toastRun } from '$lib/stores/toast'
7+
import { TokenAmount, type TokenInfo } from '$lib/utils/token'
8+
import type { IcrcAccount } from '@dfinity/oisy-wallet-signer'
9+
import type { Principal } from '@dfinity/principal'
10+
import { getToastStore } from '@skeletonlabs/skeleton'
11+
import { onMount, type SvelteComponent } from 'svelte'
12+
13+
// Props
14+
15+
interface Props {
16+
/** Exposes parent props to this component. */
17+
parent: SvelteComponent
18+
token: TokenInfo
19+
to: Principal
20+
onfinish: () => Promise<void>
21+
}
22+
23+
let { parent, to, token, onfinish }: Props = $props()
24+
25+
const toastStore = getToastStore()
26+
let loading = $state(true)
27+
let validating = $state(false)
28+
let submitting = $state(false)
29+
let topupAmount = $state(0)
30+
let account: IcrcAccount | null = $state(null)
31+
32+
function validateAmount(e: Event) {
33+
const input = e.target as HTMLInputElement
34+
35+
validating = false
36+
if (topupAmount < 1) {
37+
input.setCustomValidity(`Amount must be greater than 1 ${token.symbol}`)
38+
return
39+
}
40+
41+
if (input.value.startsWith('0')) {
42+
input.value = topupAmount.toString()
43+
}
44+
45+
input.setCustomValidity('')
46+
validating = true
47+
}
48+
49+
function onTopup(e: Event) {
50+
submitting = true
51+
toastRun(async function () {
52+
const ta = TokenAmount.fromNumber({ amount: topupAmount, token })
53+
await oisyWallet.transfer(to, token.canisterId, ta.toE8s())
54+
await onfinish()
55+
}, toastStore).finally(() => {
56+
parent['onClose'] && parent['onClose']()
57+
})
58+
}
59+
60+
onMount(() => {
61+
const { abort } = toastRun(async function () {
62+
account = await oisyWallet.connect()
63+
loading = false
64+
}, toastStore)
65+
return abort
66+
})
67+
</script>
68+
69+
<ModalCard {parent}>
70+
<div class="!mt-0 text-center text-xl font-bold"
71+
>{`Topup ${token.symbol} from OISY`}</div
72+
>
73+
74+
<form class="m-auto !mt-4 flex flex-col content-center">
75+
{#if loading}
76+
<div class="m-auto">
77+
<Loading />
78+
</div>
79+
{:else if account}
80+
<div class="space-y-2">
81+
<p>From: {account.owner}</p>
82+
<p>To: {to.toText()}</p>
83+
</div>
84+
{/if}
85+
<div class="relative mt-4">
86+
<input
87+
class="border-gray/10 peer input truncate rounded-xl bg-white/20 valid:input-success"
88+
type="number"
89+
name="amount"
90+
min="0"
91+
step="any"
92+
bind:value={topupAmount}
93+
oninput={validateAmount}
94+
placeholder="Enter amount"
95+
disabled={loading || submitting}
96+
required
97+
/>
98+
</div>
99+
</form>
100+
<footer class="m-auto !mt-6">
101+
<button
102+
class="variant-filled-primary btn w-full"
103+
disabled={loading || submitting || !validating}
104+
onclick={onTopup}
105+
>
106+
{#if submitting}
107+
<span class=""><IconCircleSpin /></span>
108+
<span>Processing...</span>
109+
{:else}
110+
<span>Topup</span>
111+
{/if}
112+
</button>
113+
</footer>
114+
</ModalCard>

src/ic_message_frontend/src/lib/components/core/WalletDetailModal.svelte

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import { getModalStore } from '@skeletonlabs/skeleton'
3131
import { onMount, type Snippet, type SvelteComponent } from 'svelte'
3232
import ImportTokenModal from './ImportTokenModal.svelte'
33+
import TopupTokenModal from './TopupTokenModal.svelte'
3334
import TransferTokenModal from './TransferTokenModal.svelte'
3435
3536
// Props
@@ -112,6 +113,22 @@
112113
})
113114
}
114115
116+
function onClickTopupPANDA() {
117+
;(modalStore as any).trigger2({
118+
type: 'component',
119+
component: {
120+
ref: TopupTokenModal,
121+
props: {
122+
to: myState!.principal,
123+
token: PANDAToken,
124+
onfinish: async () => {
125+
pandaTokenInfo.balance = await pandaLedgerAPI.balance()
126+
}
127+
}
128+
}
129+
})
130+
}
131+
115132
let deleteTokenSubmitting = $state('')
116133
async function onDeleteToken(id: string) {
117134
if (!myState) {
@@ -138,19 +155,29 @@
138155
onMount(async () => {
139156
myState = await MyMessageState.load()
140157
if (!myState.principal.isAnonymous()) {
141-
icpTokenInfo.balance = await icpLedgerAPI.balance()
142-
pandaTokenInfo.balance = await pandaLedgerAPI.balance()
143-
dmsgTokenInfo.balance = await dmsgLedgerAPI.balance()
158+
icpLedgerAPI.balance().then((balance) => {
159+
icpTokenInfo.balance = balance
160+
})
161+
162+
pandaLedgerAPI.balance().then((balance) => {
163+
pandaTokenInfo.balance = balance
164+
})
165+
166+
dmsgLedgerAPI.balance().then((balance) => {
167+
dmsgTokenInfo.balance = balance
168+
})
144169
145-
myInfo = await myState.agent.getProfile().catch(() => null)
170+
myInfo = await myState!.agent.getProfile().catch(() => null)
146171
if (myInfo) {
147172
const tokens = await myState.agent.loadTokens(myInfo.tokens)
148-
myTokens = await Promise.all(
149-
tokens.map((token) => {
150-
const api = new TokenLedgerAPI(token)
151-
return api.balance().then((balance) => ({ ...token, api, balance }))
152-
})
153-
)
173+
myTokens = tokens.map((token) => {
174+
const api = new TokenLedgerAPI(token)
175+
return { ...token, api, balance: 0n }
176+
})
177+
178+
for (const token of myTokens) {
179+
token.api.balance().then((balance) => (token.balance = balance))
180+
}
154181
}
155182
}
156183
})
@@ -231,6 +258,14 @@
231258
textValue={principal.toString()}
232259
/>
233260
</div>
261+
<button
262+
type="button"
263+
class="variant-filled-primary btn flex w-full flex-row items-center justify-center rounded-xl px-4 py-3"
264+
onclick={onClickTopupPANDA}
265+
>
266+
<span>Topup PANDA from OISY Wallet</span>
267+
</button>
268+
<hr class="!border-t-1 !border-gray/20 mx-[-24px] !mt-6 !border-dashed" />
234269
<div class="!mt-2 flex flex-col gap-0">
235270
{@render tokenItem(icpTokenInfo, icpLogo)}
236271
{@render tokenItem(pandaTokenInfo, pandaLogo)}
@@ -255,7 +290,7 @@
255290
href="https://oisy.com/transactions/?token=ICPanda&network=ICP"
256291
>
257292
<span class="*:size-5"><IconExternalLinkLine /></span>
258-
<span class="hover:underline">Buy PANDA on OISY (Fiat Currency)</span>
293+
<span class="hover:underline">Get PANDA from OISY (Fiat Money)</span>
259294
</a>
260295
<a
261296
type="button"
@@ -264,7 +299,7 @@
264299
href="https://app.icpswap.com/swap?input=ryjl3-tyaaa-aaaaa-aaaba-cai&output=druyg-tyaaa-aaaaq-aactq-cai"
265300
>
266301
<span class="*:size-5"><IconExternalLinkLine /></span>
267-
<span class="hover:underline">Buy PANDA on ICPswap</span>
302+
<span class="hover:underline">Get PANDA from ICPswap</span>
268303
</a>
269304
</div>
270305
</ModalCard>

src/ic_message_frontend/src/lib/components/messages/MyChannelList.svelte

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,21 @@
123123
width="w-10"
124124
/>
125125
</div>
126-
<div class="flex-1">
127-
<div class="flex flex-row items-center justify-between text-sm">
128-
<span>
129-
{channel.name}
130-
</span>
126+
<div class="min-w-0 flex-1">
127+
<p class="truncate text-left text-sm">
128+
{channel.name}
129+
</p>
130+
<div
131+
class="flex flex-row items-center justify-between space-x-1 text-xs text-surface-500"
132+
>
133+
<p>
134+
<span>
135+
{channel.latest_message_user.name}
136+
</span>
137+
<span>
138+
{channel.latest_message_time}
139+
</span>
140+
</p>
131141
{#if channel.my_setting.mute}
132142
<span class="text-surface-400 *:size-3"
133143
><IconNotificationOffLine /></span
@@ -136,14 +146,6 @@
136146
<span class="text-xs">{channel.my_setting.unread}</span>
137147
{/if}
138148
</div>
139-
<div class="flex flex-row space-x-1 text-xs text-surface-500">
140-
<span>
141-
{channel.latest_message_user.name}
142-
</span>
143-
<span>
144-
{channel.latest_message_time}
145-
</span>
146-
</div>
147149
</div>
148150
</button>
149151
{:else}

src/ic_message_frontend/src/lib/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const src = globalThis.location?.href || ''
22

3-
export const APP_VERSION = '2.8.3'
3+
export const APP_VERSION = '2.8.4'
44
export const IS_LOCAL = src.includes('localhost') || src.includes('127.0.0.1')
55
export const ENV = IS_LOCAL ? 'local' : 'ic'
66
export const APP_ORIGIN = IS_LOCAL

0 commit comments

Comments
 (0)