Skip to content

Commit 680ea6e

Browse files
authored
Transfer (#3981)
2 parents 2a86721 + 80768ab commit 680ea6e

38 files changed

+1651
-234
lines changed

app2/app2.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ _: {
2020
{
2121
packages = {
2222
app2 = jsPkgs.buildNpmPackage {
23-
npmDepsHash = "sha256-qo3INRx9IMbe5lfJrm/7/S++u9TVun7NqtXuvqpzCII=";
23+
npmDepsHash = "sha256-R+7qoYHPuUmBKnvBwjsb4arZQikBckKndQlSYH2lQ5I=";
2424
src = ./.;
2525
sourceRoot = "app2";
2626
npmFlags = [ "--legacy-peer-deps" ];

app2/package-lock.json

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app2/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@
88
"build": "vite build",
99
"preview": "vite preview",
1010
"prepare": "svelte-kit sync || echo ''",
11+
"postinstall": "patch-package",
1112
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
1213
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
1314
"lint": "eslint .",
1415
"test:unit": "vitest",
1516
"test": "npm run test:unit -- --run"
1617
},
1718
"devDependencies": {
19+
"@cosmjs/amino": "^0.33.0",
20+
"@cosmjs/cosmwasm-stargate": "0.33.0",
21+
"@cosmjs/proto-signing": "^0.33.0",
22+
"@cosmjs/stargate": "0.33.0",
23+
"@cosmjs/tendermint-rpc": "^0.33.0",
24+
"patch-package": "^8.0.0",
1825
"@effect/platform": "^0.77.2",
1926
"@eslint/compat": "^1.2.6",
2027
"@eslint/js": "^9.20.0",
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
diff --git a/node_modules/@cosmjs/amino/build/pubkeys.js b/node_modules/@cosmjs/amino/build/pubkeys.js
2+
index e9844ef..86101f8 100644
3+
--- a/node_modules/@cosmjs/amino/build/pubkeys.js
4+
+++ b/node_modules/@cosmjs/amino/build/pubkeys.js
5+
@@ -9,6 +9,10 @@ function isSecp256k1Pubkey(pubkey) {
6+
return pubkey.type === "tendermint/PubKeySecp256k1";
7+
}
8+
exports.isSecp256k1Pubkey = isSecp256k1Pubkey;
9+
+function isBn254Pubkey(pubkey) {
10+
+ return pubkey.type === "tendermint/PubKeyBn254";
11+
+}
12+
+exports.isBn254Pubkey = isBn254Pubkey;
13+
exports.pubkeyType = {
14+
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
15+
secp256k1: "tendermint/PubKeySecp256k1",
16+
@@ -16,6 +20,7 @@ exports.pubkeyType = {
17+
ed25519: "tendermint/PubKeyEd25519",
18+
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
19+
sr25519: "tendermint/PubKeySr25519",
20+
+ bn254: "tendermint/PubKeyBn254",
21+
multisigThreshold: "tendermint/PubKeyMultisigThreshold",
22+
};
23+
function isSinglePubkey(pubkey) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
diff --git a/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js b/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js
2+
index 29ec063..28a5c02 100644
3+
--- a/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js
4+
+++ b/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js
5+
@@ -72,7 +72,7 @@ function decodePubkey(data) {
6+
if ("Sum" in data) {
7+
// we don't need to check type because we're checking algorithm
8+
const [[algorithm, value]] = Object.entries(data.Sum.value);
9+
- (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1", `unknown pubkey type: ${algorithm}`);
10+
+ (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1" || algorithm === "bn254", `unknown pubkey type: ${algorithm}`);
11+
return {
12+
algorithm,
13+
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(value)),
14+
@@ -91,6 +91,16 @@ function decodePubkey(data) {
15+
algorithm: "secp256k1",
16+
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
17+
};
18+
+ case "tendermint/PubKeyBn254":
19+
+ return {
20+
+ algorithm: "bn254",
21+
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
22+
+ };
23+
+ case "cometbft/PubKeyBn254":
24+
+ return {
25+
+ algorithm: "bn254",
26+
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
27+
+ };
28+
default:
29+
throw new Error(`unknown pubkey type: ${data.type}`);
30+
}
31+
diff --git a/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js b/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js
32+
index 19df9de..0015044 100644
33+
--- a/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js
34+
+++ b/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js
35+
@@ -72,7 +72,7 @@ function decodePubkey(data) {
36+
if ("Sum" in data) {
37+
// we don't need to check type because we're checking algorithm
38+
const [[algorithm, value]] = Object.entries(data.Sum.value);
39+
- (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1", `unknown pubkey type: ${algorithm}`);
40+
+ (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1" || algorithm === "bn254", `unknown pubkey type: ${algorithm}`);
41+
return {
42+
algorithm,
43+
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(value)),
44+
@@ -91,6 +91,16 @@ function decodePubkey(data) {
45+
algorithm: "secp256k1",
46+
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
47+
};
48+
+ case "tendermint/PubKeyBn254":
49+
+ return {
50+
+ algorithm: "bn254",
51+
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
52+
+ };
53+
+ case "cometbft/PubKeyBn254":
54+
+ return {
55+
+ algorithm: "bn254",
56+
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
57+
+ };
58+
default:
59+
throw new Error(`unknown pubkey type: ${data.type}`);
60+
}
61+
diff --git a/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js b/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js
62+
index 257b104..dbf2240 100644
63+
--- a/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js
64+
+++ b/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js
65+
@@ -28,7 +28,7 @@ async function connectComet(endpoint) {
66+
if (version.startsWith("0.37.")) {
67+
out = tm37Client;
68+
}
69+
- else if (version.startsWith("0.38.")) {
70+
+ else if (version.startsWith("0.38.") || version.startsWith("1.0.")) {
71+
tm37Client.disconnect();
72+
out = await comet38_1.Comet38Client.connect(endpoint);
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script lang="ts">
2+
import Input from "$lib/components/ui/Input.svelte"
3+
import { transfer } from "$lib/components/Transfer/transfer.svelte.ts"
4+
import { Option } from "effect"
5+
</script>
6+
<Input id="amount"
7+
label="amount"
8+
type="text"
9+
required={true}
10+
disabled={!transfer.raw.asset}
11+
autocorrect="off"
12+
placeholder="0.00"
13+
spellcheck="false"
14+
autocomplete="off"
15+
inputmode="decimal"
16+
data-field="amount"
17+
autocapitalize="none"
18+
pattern="^[0-9]*[.]?[0-9]*$"
19+
value={transfer.raw.amount}
20+
oninput={(event) => {
21+
const input = event.currentTarget;
22+
const value = input.value;
23+
const maxDecimals = Option.isSome(transfer.baseToken)
24+
? (transfer.baseToken.value.representations[0]?.decimals ?? 0)
25+
: 0;
26+
27+
if (value === '' || (/^\d*\.?\d*$/.test(value) &&
28+
(value.includes('.')
29+
? value.split('.')[1].length <= maxDecimals
30+
: true)
31+
)) {
32+
transfer.raw.updateField('amount', event);
33+
} else {
34+
input.value = transfer.raw.amount;
35+
}
36+
}}
37+
class="text-center"
38+
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script lang="ts">
2+
import Label from "$lib/components/ui/Label.svelte"
3+
import { Option } from "effect"
4+
import { tokensStore } from "$lib/stores/tokens.svelte.ts"
5+
import Input from "$lib/components/ui/Input.svelte"
6+
import { fade, fly } from "svelte/transition"
7+
import { transfer } from "$lib/components/Transfer/transfer.svelte.ts"
8+
9+
let open = $state(false)
10+
let searchQuery = $state("")
11+
12+
function ensureTokensForChain() {
13+
if (Option.isNone(transfer.sourceChain)) return
14+
15+
const chainId = transfer.sourceChain.value.universal_chain_id
16+
if (!chainId) return
17+
18+
const tokenData = tokensStore.getData(chainId)
19+
if (Option.isNone(tokenData)) {
20+
tokensStore.fetchTokens(chainId)
21+
}
22+
}
23+
24+
$effect(() => {
25+
if (Option.isSome(transfer.sourceChain)) {
26+
ensureTokensForChain()
27+
}
28+
})
29+
30+
const filteredTokens = $derived.by(() => {
31+
const query = searchQuery.toLowerCase()
32+
return Option.getOrElse(transfer.baseTokens, () => []).filter(
33+
token =>
34+
token.denom.toLowerCase().includes(query) ||
35+
(token.representations[0]?.name?.toLowerCase() || "").includes(query)
36+
)
37+
})
38+
</script>
39+
40+
{#if open}
41+
<div
42+
transition:fade={{ duration: 500 }}
43+
class="absolute top-0 left-0 w-full h-full z-30 flex items-center justify-center bg-zinc-900"
44+
>
45+
<div
46+
transition:fly={{ y: 50, duration: 500, opacity: 0, delay: 100 }}
47+
class="w-full h-full rounded-lg p-4 shadow-lg flex flex-col"
48+
>
49+
<!-- Search Bar -->
50+
<Input
51+
label="Search"
52+
type="text"
53+
placeholder="Search by name or paste address"
54+
value={searchQuery}
55+
oninput={(e) => (searchQuery = (e.currentTarget as HTMLInputElement).value)}
56+
/>
57+
58+
<!-- Scrollable Token List -->
59+
{#if Option.isSome(transfer.sourceChain)}
60+
{@const tokenData = tokensStore.getData(transfer.sourceChain.value.universal_chain_id)}
61+
{@const error = tokensStore.getError(transfer.sourceChain.value.universal_chain_id)}
62+
63+
{#if Option.isSome(error)}
64+
<p class="text-center text-red-500">Error: {error.value.message}</p>
65+
{:else if Option.isNone(tokenData)}
66+
<p class="text-center text-zinc-400">Loading tokens...</p>
67+
{:else}
68+
<ul class="space-y-2 overflow-y-auto flex-1">
69+
{#each filteredTokens as token}
70+
<li>
71+
<button
72+
class="w-full p-2 text-left text-zinc-200 hover:bg-[#2a3535] hover:text-white focus:outline-none focus:bg-[#2a3535] transition-all duration-200"
73+
onclick={() => {
74+
transfer.raw.updateField("asset", token.denom);
75+
open = false;
76+
searchQuery = "";
77+
}}
78+
>
79+
<span class="font-medium">
80+
{token.representations[0]?.name ?? token.denom}
81+
</span>
82+
{#if token.representations[0]?.name}
83+
<span class="text-sm text-zinc-400 ml-2">({token.denom})</span>
84+
{/if}
85+
</button>
86+
</li>
87+
{/each}
88+
</ul>
89+
{/if}
90+
{:else}
91+
<p class="text-center text-zinc-400">No chain selected</p>
92+
{/if}
93+
</div>
94+
</div>
95+
{/if}
96+
97+
<div class="space-y-1">
98+
<Label>Asset</Label>
99+
<button
100+
disabled={Option.isNone(transfer.sourceChain)}
101+
onclick={() => (open = !open)}
102+
class="w-full p-2 rounded-lg border border-zinc-600 bg-zinc-700 text-zinc-200 hover:bg-zinc-600 hover:border-zinc-500 focus:outline-none focus:ring-2 focus:ring-sky-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 cursor-pointer"
103+
>
104+
{#if transfer.raw.asset && Option.isNone(transfer.baseToken)}
105+
<span class="flex items-center justify-center">
106+
<svg
107+
class="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
108+
xmlns="http://www.w3.org/2000/svg"
109+
fill="none"
110+
viewBox="0 0 24 24"
111+
>
112+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
113+
<path
114+
class="opacity-75"
115+
fill="currentColor"
116+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
117+
></path>
118+
</svg>
119+
Loading...
120+
</span>
121+
{:else}
122+
{Option.match(transfer.baseToken, {
123+
onNone: () => transfer.raw.asset || "Select asset",
124+
onSome: (token) => token.representations[0]?.name ?? token.denom
125+
})}
126+
{/if}
127+
</button>
128+
</div>

0 commit comments

Comments
 (0)