@idos-network/kwil-infra 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +47 -0
- package/.turbo/turbo-test.log +126 -0
- package/.turbo/turbo-typecheck.log +5 -0
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/actions/index.d.mts +2 -0
- package/dist/actions/index.mjs +1371 -0
- package/dist/actions/index.mjs.map +1 -0
- package/dist/facesign/index.d.mts +2 -0
- package/dist/facesign/index.mjs +2 -0
- package/dist/facesign-signer-ADd4EASX.d.mts +29 -0
- package/dist/facesign-signer-ADd4EASX.d.mts.map +1 -0
- package/dist/facesign-signer-BY9Nti9F.mjs +244 -0
- package/dist/facesign-signer-BY9Nti9F.mjs.map +1 -0
- package/dist/index-Ci6QhKJo.d.mts +644 -0
- package/dist/index-Ci6QhKJo.d.mts.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +429 -0
- package/dist/index.mjs.map +1 -0
- package/dist/signature-verification/index.d.mts +14 -0
- package/dist/signature-verification/index.d.mts.map +1 -0
- package/dist/signature-verification/index.mjs +140 -0
- package/dist/signature-verification/index.mjs.map +1 -0
- package/dist/xrp/utils.d.mts +1165 -0
- package/dist/xrp/utils.d.mts.map +1 -0
- package/dist/xrp/utils.mjs +105 -0
- package/dist/xrp/utils.mjs.map +1 -0
- package/dist/xrpl-credentials/index.d.mts +179 -0
- package/dist/xrpl-credentials/index.d.mts.map +1 -0
- package/dist/xrpl-credentials/index.mjs +224 -0
- package/dist/xrpl-credentials/index.mjs.map +1 -0
- package/package.json +93 -0
- package/src/actions/actions.ts +2197 -0
- package/src/actions/index.ts +55 -0
- package/src/create-kwil-client.ts +144 -0
- package/src/create-kwil-signer.ts +270 -0
- package/src/facesign/facesign-signer.ts +327 -0
- package/src/facesign/index.ts +2 -0
- package/src/index.ts +7 -0
- package/src/near/create-near-wallet-kwil-signer.ts +245 -0
- package/src/near/get-config.ts +43 -0
- package/src/near/nep413.ts +62 -0
- package/src/near/signature-verification.ts +85 -0
- package/src/signature-verification/evm.ts +33 -0
- package/src/signature-verification/facesign.ts +19 -0
- package/src/signature-verification/index.test.ts +110 -0
- package/src/signature-verification/index.ts +72 -0
- package/src/signature-verification/stellar.ts +31 -0
- package/src/xrp/signature-verification.ts +18 -0
- package/src/xrp/signer.ts +43 -0
- package/src/xrp/utils.ts +178 -0
- package/src/xrpl-credentials/accept.ts +143 -0
- package/src/xrpl-credentials/create.ts +199 -0
- package/src/xrpl-credentials/index.ts +12 -0
- package/tsconfig.json +33 -0
- package/tsdown.config.ts +13 -0
- package/vitest.config.ts +6 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
borshDeserialize,
|
|
3
|
+
borshSerialize,
|
|
4
|
+
bs58Decode,
|
|
5
|
+
hexDecode,
|
|
6
|
+
sha256Hash,
|
|
7
|
+
} from "@idos-network/utils/codecs";
|
|
8
|
+
import nacl from "tweetnacl";
|
|
9
|
+
|
|
10
|
+
interface NEP413Payload {
|
|
11
|
+
tag: number;
|
|
12
|
+
message: string;
|
|
13
|
+
nonce: number[];
|
|
14
|
+
recipient: string;
|
|
15
|
+
callbackUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const verifyNearSignature = async (
|
|
19
|
+
message: string,
|
|
20
|
+
signature: string,
|
|
21
|
+
publicKey: string,
|
|
22
|
+
): Promise<boolean> => {
|
|
23
|
+
try {
|
|
24
|
+
// Parse the NEAR public key format: "ed25519:base58_encoded_key"
|
|
25
|
+
const pieces = publicKey.split(":");
|
|
26
|
+
if (pieces.length !== 2 || pieces[0] !== "ed25519") {
|
|
27
|
+
console.warn("Invalid NEAR public key format:", publicKey);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Extract raw public key bytes (am trying to match the logic I did in Backend)
|
|
32
|
+
const publicKeyBytes = bs58Decode(pieces[1]);
|
|
33
|
+
if (publicKeyBytes.length !== 32) {
|
|
34
|
+
console.warn("Invalid NEAR public key length:", publicKeyBytes.length);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Decode the signature from hex
|
|
39
|
+
const signatureBytes = hexDecode(signature);
|
|
40
|
+
|
|
41
|
+
// Deserialize the signature (matching Go's deserializeSignature)
|
|
42
|
+
const payloadLength = new DataView(signatureBytes.buffer).getUint16(0, false);
|
|
43
|
+
const payloadBytes = signatureBytes.slice(2, 2 + payloadLength);
|
|
44
|
+
const actualSignature = signatureBytes.slice(2 + payloadLength);
|
|
45
|
+
|
|
46
|
+
if (actualSignature.length !== 64) {
|
|
47
|
+
console.warn("Invalid signature length:", actualSignature.length);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Deserialize the payload to extract nonce, recipient, callbackUrl
|
|
52
|
+
const payloadSchema = {
|
|
53
|
+
struct: {
|
|
54
|
+
tag: "u32",
|
|
55
|
+
message: "string",
|
|
56
|
+
nonce: { array: { type: "u8", len: 32 } },
|
|
57
|
+
recipient: "string",
|
|
58
|
+
callbackUrl: { option: "string" },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const deserializedPayload = borshDeserialize(payloadSchema, payloadBytes) as NEP413Payload;
|
|
63
|
+
|
|
64
|
+
// Reconstruct the payload with tag and message (matching Go implementation)
|
|
65
|
+
const reconstructedPayload: NEP413Payload = {
|
|
66
|
+
tag: 2147484061, // 2**31 + 413
|
|
67
|
+
message: message, // Use the provided message
|
|
68
|
+
nonce: deserializedPayload.nonce,
|
|
69
|
+
recipient: deserializedPayload.recipient,
|
|
70
|
+
callbackUrl: deserializedPayload.callbackUrl,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Serialize the entire payload (matching Go's borsch.Serialize(*payload))
|
|
74
|
+
const fullPayloadBytes = borshSerialize(payloadSchema, reconstructedPayload);
|
|
75
|
+
// Hash the payload (matching Go's sha256.Sum256(payloadBytes))
|
|
76
|
+
const hash = sha256Hash(fullPayloadBytes);
|
|
77
|
+
// Raw ed25519 verification using nacl (matching Go's ed25519.Verify)
|
|
78
|
+
const isValid = nacl.sign.detached.verify(hash, actualSignature, publicKeyBytes);
|
|
79
|
+
|
|
80
|
+
return isValid;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn("NEAR signature verification failed:", error);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const verifyEvmSignature = async (
|
|
2
|
+
message: string,
|
|
3
|
+
signature: `0x${string}`,
|
|
4
|
+
address: `0x${string}`,
|
|
5
|
+
): Promise<boolean> => {
|
|
6
|
+
let wagmi: typeof import("@wagmi/core");
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
wagmi = await import("@wagmi/core");
|
|
10
|
+
} catch (_e) {
|
|
11
|
+
throw new Error("Can't load @wagmi/core");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let chains: typeof import("@wagmi/core/chains");
|
|
15
|
+
try {
|
|
16
|
+
chains = await import("@wagmi/core/chains");
|
|
17
|
+
} catch (_e) {
|
|
18
|
+
throw new Error("Can't load @wagmi/core/chains");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const wagmiConfig = wagmi.createConfig({
|
|
22
|
+
chains: [chains.mainnet],
|
|
23
|
+
transports: {
|
|
24
|
+
[chains.mainnet.id]: wagmi.http(),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return wagmi.verifyMessage(wagmiConfig, {
|
|
29
|
+
address,
|
|
30
|
+
message,
|
|
31
|
+
signature,
|
|
32
|
+
});
|
|
33
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { hexDecode, utf8Encode } from "@idos-network/utils/codecs";
|
|
2
|
+
import nacl from "tweetnacl";
|
|
3
|
+
|
|
4
|
+
export const verifyFaceSignSignature = (
|
|
5
|
+
message: string,
|
|
6
|
+
signature: string,
|
|
7
|
+
publicKey: string,
|
|
8
|
+
): boolean => {
|
|
9
|
+
try {
|
|
10
|
+
return nacl.sign.detached.verify(
|
|
11
|
+
utf8Encode(message),
|
|
12
|
+
hexDecode(signature),
|
|
13
|
+
hexDecode(publicKey),
|
|
14
|
+
);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.warn("FaceSign signature verification failed:", error);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { hexEncode, utf8Encode } from "@idos-network/utils/codecs";
|
|
2
|
+
import nacl from "tweetnacl";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
vi.mock("./evm", () => ({ verifyEvmSignature: vi.fn() }));
|
|
6
|
+
vi.mock("./stellar", () => ({ verifyStellarSignature: vi.fn() }));
|
|
7
|
+
vi.mock("../near/signature-verification", () => ({ verifyNearSignature: vi.fn() }));
|
|
8
|
+
vi.mock("../xrp/signature-verification", () => ({ verifyRippleSignature: vi.fn() }));
|
|
9
|
+
|
|
10
|
+
import { verifyNearSignature } from "../near/signature-verification.js";
|
|
11
|
+
import { verifyRippleSignature } from "../xrp/signature-verification.js";
|
|
12
|
+
import { verifyEvmSignature } from "./evm.js";
|
|
13
|
+
import { verifySignature } from "./index.js";
|
|
14
|
+
import { verifyStellarSignature } from "./stellar.js";
|
|
15
|
+
|
|
16
|
+
describe("verifySignature", () => {
|
|
17
|
+
it("routes EVM verification", async () => {
|
|
18
|
+
const evmMock = vi.mocked(verifyEvmSignature);
|
|
19
|
+
evmMock.mockResolvedValueOnce(true);
|
|
20
|
+
|
|
21
|
+
const result = await verifySignature({
|
|
22
|
+
address: "0xabc",
|
|
23
|
+
signature: "0xsig",
|
|
24
|
+
message: "hello",
|
|
25
|
+
wallet_type: "EVM",
|
|
26
|
+
public_key: [],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(result).toBe(true);
|
|
30
|
+
expect(evmMock).toHaveBeenCalledWith("hello", "0xsig", "0xabc");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("requires public_key for non-EVM wallets", async () => {
|
|
34
|
+
await expect(
|
|
35
|
+
verifySignature({
|
|
36
|
+
address: "alice.near",
|
|
37
|
+
signature: "sig",
|
|
38
|
+
message: "hello",
|
|
39
|
+
wallet_type: "NEAR",
|
|
40
|
+
public_key: [],
|
|
41
|
+
}),
|
|
42
|
+
).rejects.toThrow("Wallet public_key is required for non-EVM wallets");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("routes NEAR verification", async () => {
|
|
46
|
+
const nearMock = vi.mocked(verifyNearSignature);
|
|
47
|
+
nearMock.mockResolvedValueOnce(true);
|
|
48
|
+
|
|
49
|
+
const result = await verifySignature({
|
|
50
|
+
address: "alice.near",
|
|
51
|
+
signature: "sig",
|
|
52
|
+
message: "hello",
|
|
53
|
+
wallet_type: "NEAR",
|
|
54
|
+
public_key: ["ed25519:abc"],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(result).toBe(true);
|
|
58
|
+
expect(nearMock).toHaveBeenCalledWith("hello", "sig", "ed25519:abc");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("routes Stellar verification", async () => {
|
|
62
|
+
const stellarMock = vi.mocked(verifyStellarSignature);
|
|
63
|
+
stellarMock.mockResolvedValueOnce(true);
|
|
64
|
+
|
|
65
|
+
const result = await verifySignature({
|
|
66
|
+
address: "GABC",
|
|
67
|
+
signature: "sig",
|
|
68
|
+
message: "hello",
|
|
69
|
+
wallet_type: "Stellar",
|
|
70
|
+
public_key: ["GABC"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result).toBe(true);
|
|
74
|
+
expect(stellarMock).toHaveBeenCalledWith("hello", "sig", "GABC");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("returns false when verifier throws", async () => {
|
|
78
|
+
const xrpMock = vi.mocked(verifyRippleSignature);
|
|
79
|
+
xrpMock.mockImplementationOnce(() => {
|
|
80
|
+
throw new Error("boom");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = await verifySignature({
|
|
84
|
+
address: "rAddress",
|
|
85
|
+
signature: "sig",
|
|
86
|
+
message: "hello",
|
|
87
|
+
wallet_type: "XRPL",
|
|
88
|
+
public_key: ["pk"],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(result).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("routes FaceSign verification", async () => {
|
|
95
|
+
const keyPair = nacl.sign.keyPair();
|
|
96
|
+
const publicKey = hexEncode(keyPair.publicKey);
|
|
97
|
+
const message = utf8Encode("hello");
|
|
98
|
+
const signature = nacl.sign.detached(message, keyPair.secretKey);
|
|
99
|
+
|
|
100
|
+
const result = await verifySignature({
|
|
101
|
+
address: "faceSignAddress",
|
|
102
|
+
signature: hexEncode(signature),
|
|
103
|
+
message: "hello",
|
|
104
|
+
wallet_type: "FaceSign",
|
|
105
|
+
public_key: [publicKey],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(result).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import invariant from "tiny-invariant";
|
|
2
|
+
|
|
3
|
+
import type { WalletType } from "../actions";
|
|
4
|
+
|
|
5
|
+
import { verifyNearSignature } from "../near/signature-verification";
|
|
6
|
+
import { verifyRippleSignature } from "../xrp/signature-verification";
|
|
7
|
+
import { verifyEvmSignature } from "./evm";
|
|
8
|
+
import { verifyFaceSignSignature } from "./facesign";
|
|
9
|
+
import { verifyStellarSignature } from "./stellar";
|
|
10
|
+
|
|
11
|
+
export interface WalletSignature {
|
|
12
|
+
address: string;
|
|
13
|
+
signature: string;
|
|
14
|
+
message: string;
|
|
15
|
+
wallet_type: WalletType;
|
|
16
|
+
public_key?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const verifySignature = async (walletPayload: WalletSignature): Promise<boolean> => {
|
|
20
|
+
invariant(walletPayload.address, "Wallet address is required");
|
|
21
|
+
invariant(walletPayload.message, "Wallet message is required");
|
|
22
|
+
invariant(walletPayload.signature, "Wallet signature is required");
|
|
23
|
+
invariant(walletPayload.wallet_type, "Wallet type is required");
|
|
24
|
+
|
|
25
|
+
if (walletPayload.wallet_type !== "EVM") {
|
|
26
|
+
invariant(walletPayload.public_key?.[0], "Wallet public_key is required for non-EVM wallets");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
switch (walletPayload.wallet_type) {
|
|
31
|
+
case "EVM":
|
|
32
|
+
return verifyEvmSignature(
|
|
33
|
+
walletPayload.message,
|
|
34
|
+
walletPayload.signature as `0x${string}`,
|
|
35
|
+
walletPayload.address as `0x${string}`,
|
|
36
|
+
);
|
|
37
|
+
case "NEAR":
|
|
38
|
+
return verifyNearSignature(
|
|
39
|
+
walletPayload.message,
|
|
40
|
+
walletPayload.signature,
|
|
41
|
+
// @ts-expect-error - this has been checked above
|
|
42
|
+
walletPayload.public_key[0],
|
|
43
|
+
);
|
|
44
|
+
case "XRPL":
|
|
45
|
+
return verifyRippleSignature(
|
|
46
|
+
walletPayload.message,
|
|
47
|
+
walletPayload.signature,
|
|
48
|
+
// @ts-expect-error - this has been checked above
|
|
49
|
+
walletPayload.public_key[0],
|
|
50
|
+
);
|
|
51
|
+
case "Stellar":
|
|
52
|
+
return await verifyStellarSignature(
|
|
53
|
+
walletPayload.message,
|
|
54
|
+
walletPayload.signature,
|
|
55
|
+
// @ts-expect-error - this has been checked above
|
|
56
|
+
walletPayload.public_key[0],
|
|
57
|
+
);
|
|
58
|
+
case "FaceSign":
|
|
59
|
+
return verifyFaceSignSignature(
|
|
60
|
+
walletPayload.message,
|
|
61
|
+
walletPayload.signature,
|
|
62
|
+
// @ts-expect-error - this has been checked above
|
|
63
|
+
walletPayload.public_key[0],
|
|
64
|
+
);
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Unsupported wallet type: ${walletPayload.wallet_type}`);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.warn("Error verifying signature", error);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const verifyStellarSignature = async (
|
|
2
|
+
message: string,
|
|
3
|
+
signature: string,
|
|
4
|
+
publicKey: string,
|
|
5
|
+
): Promise<boolean> => {
|
|
6
|
+
let stellar: typeof import("@stellar/stellar-sdk");
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
stellar = await import("@stellar/stellar-sdk");
|
|
10
|
+
} catch (e) {
|
|
11
|
+
throw new Error("Can't load `@stellar/stellar-sdk`", { cause: e });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Convert hex signature to buffer
|
|
16
|
+
const signatureBuffer = Buffer.from(signature, "hex");
|
|
17
|
+
|
|
18
|
+
// `Keypair.fromPublicKey()` expects StrKey format, but we're passing as hex (derivePublicKey result).
|
|
19
|
+
const publicKeyBuffer = Buffer.from(publicKey, "hex");
|
|
20
|
+
const strKeyAddress = stellar.StrKey.encodeEd25519PublicKey(publicKeyBuffer);
|
|
21
|
+
|
|
22
|
+
const keypair = stellar.Keypair.fromPublicKey(strKeyAddress);
|
|
23
|
+
|
|
24
|
+
const result = keypair.verify(Buffer.from(message), signatureBuffer);
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn("Stellar signature verification failed:", error);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { hexEncode, utf8Encode } from "@idos-network/utils/codecs";
|
|
2
|
+
|
|
3
|
+
export const verifyRippleSignature = async (
|
|
4
|
+
message: string,
|
|
5
|
+
signature: string,
|
|
6
|
+
publicKey: string,
|
|
7
|
+
): Promise<boolean> => {
|
|
8
|
+
let xrpKeypair: typeof import("ripple-keypairs");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
xrpKeypair = await import("ripple-keypairs");
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw new Error("Can't load `ripple-keypairs`", { cause: e });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const messageHex = hexEncode(utf8Encode(message));
|
|
17
|
+
return xrpKeypair.verify(messageHex, signature, publicKey);
|
|
18
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type * as GemWallet from "@gemwallet/api";
|
|
2
|
+
import type { Store } from "@idos-network/utils/store";
|
|
3
|
+
import type { Xumm } from "xumm";
|
|
4
|
+
|
|
5
|
+
import { KwilSigner } from "@idos-network/kwil-js";
|
|
6
|
+
|
|
7
|
+
import type { KwilActionClient } from "../create-kwil-client";
|
|
8
|
+
|
|
9
|
+
import { getXrpTxHash } from "./utils";
|
|
10
|
+
|
|
11
|
+
export async function createXrpKwilSigner(
|
|
12
|
+
wallet: Xumm | typeof GemWallet,
|
|
13
|
+
currentAddress: string,
|
|
14
|
+
store: Store,
|
|
15
|
+
kwilClient: KwilActionClient,
|
|
16
|
+
walletPublicKey: string,
|
|
17
|
+
): Promise<KwilSigner> {
|
|
18
|
+
const storedAddress = await store.get<string>("signer-address");
|
|
19
|
+
const storePublicKey = await store.get<string>("signer-public-key");
|
|
20
|
+
|
|
21
|
+
if (storedAddress !== currentAddress || (storePublicKey && storePublicKey !== walletPublicKey)) {
|
|
22
|
+
try {
|
|
23
|
+
// HEADS UP: for some reason logoutKGW fails on xrp
|
|
24
|
+
// storePublicKey && await kwilClient.client.auth.logoutKGW();
|
|
25
|
+
console.log("logoutKGW", kwilClient);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("Failed to logout KGW:", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await store.set("signer-address", currentAddress);
|
|
32
|
+
await store.set("signer-public-key", walletPublicKey);
|
|
33
|
+
|
|
34
|
+
const signer = async (message: string | Uint8Array): Promise<Uint8Array> => {
|
|
35
|
+
const signature = await getXrpTxHash(message, wallet);
|
|
36
|
+
if (!signature) {
|
|
37
|
+
throw new Error("Failed to sign transaction with XRP");
|
|
38
|
+
}
|
|
39
|
+
return Buffer.from(signature, "hex");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return new KwilSigner(signer, walletPublicKey, "xrpl");
|
|
43
|
+
}
|
package/src/xrp/utils.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type * as GemWallet from "@gemwallet/api";
|
|
2
|
+
import type { Xumm } from "xumm";
|
|
3
|
+
export type WalletName = "XAMAN" | "GEM";
|
|
4
|
+
|
|
5
|
+
export interface WalletConfig {
|
|
6
|
+
name: WalletName;
|
|
7
|
+
instance: Xumm | typeof GemWallet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type XamanConfig = {
|
|
11
|
+
name: "XAMAN";
|
|
12
|
+
instance: Xumm;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type GemConfig = {
|
|
16
|
+
name: "GEM";
|
|
17
|
+
instance: typeof GemWallet;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type WalletInitConfig = XamanConfig | GemConfig;
|
|
21
|
+
|
|
22
|
+
type XrpWallet = "Xumm" | "GemWallet";
|
|
23
|
+
|
|
24
|
+
export function createXummPayload(message: string): Record<string, unknown> {
|
|
25
|
+
const memoData = Buffer.from(message).toString("hex"); // Encode message as hex for Memos
|
|
26
|
+
return {
|
|
27
|
+
custom_meta: {
|
|
28
|
+
instruction: message,
|
|
29
|
+
},
|
|
30
|
+
txjson: {
|
|
31
|
+
TransactionType: "SignIn",
|
|
32
|
+
Memos: [
|
|
33
|
+
{
|
|
34
|
+
Memo: {
|
|
35
|
+
MemoData: memoData,
|
|
36
|
+
MemoType: Buffer.from("idOS").toString("hex"), // Optional type identifier
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function signXummTx(xummInstance: Xumm, payload: Record<string, unknown>): Promise<string> {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- xumm payload type is not easy to import
|
|
47
|
+
xummInstance.payload?.createAndSubscribe(payload as unknown as any, async (event) => {
|
|
48
|
+
if (!event.payload.response.hex) return;
|
|
49
|
+
const hex = event.payload.response.hex;
|
|
50
|
+
resolve(hex);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getXummPublicKey(wallet: Xumm): Promise<string | undefined> {
|
|
56
|
+
const payload = createXummPayload("Sign request from idOS");
|
|
57
|
+
const txHash = await signXummTx(wallet, payload);
|
|
58
|
+
|
|
59
|
+
let decode: typeof import("xrpl").decode;
|
|
60
|
+
try {
|
|
61
|
+
({ decode } = await import("xrpl"));
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'Missing optional peer dependency "xrpl". Install it to use Xumm public-key extraction.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let decodedTx: { SigningPubKey?: string };
|
|
69
|
+
try {
|
|
70
|
+
decodedTx = decode(txHash);
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error("Failed to decode Xumm transaction payload.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!decodedTx.SigningPubKey) {
|
|
76
|
+
throw new Error("SigningPubKey not found in decoded Xumm transaction.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return decodedTx.SigningPubKey;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function signGemWalletTx(
|
|
83
|
+
gemWalletInstance: typeof GemWallet,
|
|
84
|
+
message: string,
|
|
85
|
+
): Promise<string | undefined> {
|
|
86
|
+
return gemWalletInstance.signMessage(message).then((response) => response.result?.signedMessage);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function getGemWalletPublicKey(
|
|
90
|
+
wallet: typeof GemWallet,
|
|
91
|
+
): Promise<{ address: string; publicKey: string } | undefined> {
|
|
92
|
+
if ("isInstalled" in wallet) {
|
|
93
|
+
await wallet.isInstalled();
|
|
94
|
+
const { publicKey, address } = await wallet
|
|
95
|
+
.getPublicKey()
|
|
96
|
+
.then((res) => res.result as { publicKey: string; address: string });
|
|
97
|
+
return { address, publicKey };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function getXrpAddress(wallet: Xumm | typeof GemWallet): Promise<string | undefined> {
|
|
102
|
+
if ("isInstalled" in wallet) {
|
|
103
|
+
await wallet.isInstalled();
|
|
104
|
+
return wallet.getAddress().then((response) => response.result?.address);
|
|
105
|
+
}
|
|
106
|
+
return wallet.user.account;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Type guard to check if the wallet is an Xumm wallet
|
|
110
|
+
export function looksLikeXrpWallet(wallet: unknown): wallet is Xumm | typeof GemWallet {
|
|
111
|
+
return (
|
|
112
|
+
wallet !== null &&
|
|
113
|
+
typeof wallet === "object" &&
|
|
114
|
+
("authorize" in wallet || "isInstalled" in wallet)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function getXrpWalletType(object: Record<string, unknown>): Promise<XrpWallet> {
|
|
119
|
+
if ("authorize" in object) {
|
|
120
|
+
return "Xumm";
|
|
121
|
+
}
|
|
122
|
+
if ("isInstalled" in object) {
|
|
123
|
+
return "GemWallet";
|
|
124
|
+
}
|
|
125
|
+
throw new Error("Unknown wallet type");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function getXrpTxHash(
|
|
129
|
+
message: string | Uint8Array,
|
|
130
|
+
wallet: Xumm | typeof GemWallet,
|
|
131
|
+
): Promise<string | undefined> {
|
|
132
|
+
let messageString: string;
|
|
133
|
+
|
|
134
|
+
const xrpWalletType = await getXrpWalletType(wallet as Record<string, unknown>);
|
|
135
|
+
|
|
136
|
+
messageString = typeof message === "string" ? message : Buffer.from(message).toString("utf8"); // Decode Uint8Array to string
|
|
137
|
+
if (xrpWalletType === "Xumm") {
|
|
138
|
+
const memoData = Buffer.from(messageString).toString("hex"); // Encode message as hex for Memos
|
|
139
|
+
|
|
140
|
+
const payload = {
|
|
141
|
+
custom_meta: {
|
|
142
|
+
instruction: messageString,
|
|
143
|
+
},
|
|
144
|
+
txjson: {
|
|
145
|
+
TransactionType: "SignIn",
|
|
146
|
+
Memos: [
|
|
147
|
+
{
|
|
148
|
+
Memo: {
|
|
149
|
+
MemoData: memoData,
|
|
150
|
+
MemoType: Buffer.from("idOS").toString("hex"), // Optional type identifier
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
const xummTx = await signXummTx(wallet as Xumm, payload);
|
|
157
|
+
console.log("xummTx", xummTx);
|
|
158
|
+
} else if (xrpWalletType === "GemWallet") {
|
|
159
|
+
const signature = await signGemWalletTx(wallet as typeof GemWallet, messageString);
|
|
160
|
+
if (!signature) {
|
|
161
|
+
throw new Error("Failed to sign transaction with GemWallet");
|
|
162
|
+
}
|
|
163
|
+
return signature;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function getXrpPublicKey(
|
|
168
|
+
wallet: Xumm | typeof GemWallet,
|
|
169
|
+
): Promise<{ address: string; publicKey: string } | undefined> {
|
|
170
|
+
const xrpWalletType = await getXrpWalletType(wallet as Record<string, unknown>);
|
|
171
|
+
if (xrpWalletType === "Xumm") {
|
|
172
|
+
// @todo: should we remove xaman wallet support?
|
|
173
|
+
const publicKey = await getXummPublicKey(wallet as Xumm);
|
|
174
|
+
const address = await getXrpAddress(wallet);
|
|
175
|
+
return { address: address as string, publicKey: publicKey as string };
|
|
176
|
+
}
|
|
177
|
+
return getGemWalletPublicKey(wallet as typeof GemWallet);
|
|
178
|
+
}
|