@talismn/solana 0.0.10 → 1.0.1
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/LICENSE +253 -674
- package/dist/index.d.mts +195 -63
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +195 -63
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +198 -186
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +181 -154
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,199 +1,211 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
SOLANA_CHAINS: () => SOLANA_CHAINS,
|
|
24
|
-
deserializeTransaction: () => deserializeTransaction,
|
|
25
|
-
getKeypair: () => getKeypair,
|
|
26
|
-
getSolNetworkId: () => getSolNetworkId,
|
|
27
|
-
isVersionedTransaction: () => isVersionedTransaction,
|
|
28
|
-
parseTransactionInfo: () => parseTransactionInfo,
|
|
29
|
-
serializeTransaction: () => serializeTransaction,
|
|
30
|
-
solInstructionFromJson: () => solInstructionFromJson,
|
|
31
|
-
solInstructionToJson: () => solInstructionToJson,
|
|
32
|
-
txToHumanJSON: () => txToHumanJSON
|
|
33
|
-
});
|
|
34
|
-
module.exports = __toCommonJS(index_exports);
|
|
35
|
-
|
|
36
|
-
// src/utils/chains.ts
|
|
37
|
-
var SOLANA_CHAINS = [
|
|
38
|
-
"solana:mainnet",
|
|
39
|
-
"solana:devnet",
|
|
40
|
-
"solana:testnet",
|
|
41
|
-
"solana:localnet"
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _solana_kit = require("@solana/kit");
|
|
3
|
+
let _talismn_crypto = require("@talismn/crypto");
|
|
4
|
+
//#region src/utils/chains.ts
|
|
5
|
+
const SOLANA_CHAINS = [
|
|
6
|
+
"solana:mainnet",
|
|
7
|
+
"solana:devnet",
|
|
8
|
+
"solana:testnet",
|
|
9
|
+
"solana:localnet"
|
|
42
10
|
];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
case "solana:localnet":
|
|
52
|
-
return "solana-localnet";
|
|
53
|
-
default:
|
|
54
|
-
throw new Error(`Unknown Solana chain: ${chain}`);
|
|
55
|
-
}
|
|
11
|
+
const getSolNetworkId = (chain) => {
|
|
12
|
+
switch (chain) {
|
|
13
|
+
case "solana:mainnet": return "solana-mainnet";
|
|
14
|
+
case "solana:devnet": return "solana-devnet";
|
|
15
|
+
case "solana:testnet": return "solana-testnet";
|
|
16
|
+
case "solana:localnet": return "solana-localnet";
|
|
17
|
+
default: throw new Error(`Unknown Solana chain: ${chain}`);
|
|
18
|
+
}
|
|
56
19
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/utils/offchainMessage.ts
|
|
22
|
+
const SIGNING_DOMAIN = new Uint8Array([255, ...Array.from("solana offchain", (c) => c.charCodeAt(0))]);
|
|
23
|
+
const APPLICATION_DOMAIN_LENGTH = 32;
|
|
24
|
+
const PUBLIC_KEY_LENGTH = 32;
|
|
25
|
+
const HEADER_LENGTH = SIGNING_DOMAIN.length + 1 + APPLICATION_DOMAIN_LENGTH + 1 + 1 + PUBLIC_KEY_LENGTH + 2;
|
|
26
|
+
/** Hardware wallet compatible v0 messages are capped at 1232 bytes total, header included */
|
|
27
|
+
const MAX_MESSAGE_LENGTH = 1232 - HEADER_LENGTH;
|
|
28
|
+
const FORMAT_RESTRICTED_ASCII = 0;
|
|
29
|
+
const FORMAT_LIMITED_UTF8 = 1;
|
|
30
|
+
const getMessageFormat = (message) => {
|
|
31
|
+
if (message.every((byte) => byte >= 32 && byte <= 126)) return FORMAT_RESTRICTED_ASCII;
|
|
32
|
+
try {
|
|
33
|
+
new TextDecoder("utf-8", { fatal: true }).decode(message);
|
|
34
|
+
return FORMAT_LIMITED_UTF8;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
66
38
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Wraps a raw message in the off-chain message envelope that hardware wallets sign.
|
|
41
|
+
* Returns `null` if the message cannot be wrapped (binary content, empty, or too long).
|
|
42
|
+
*/
|
|
43
|
+
const serializeOffchainMessage = (message, signerPublicKey) => {
|
|
44
|
+
if (message.length === 0 || message.length > MAX_MESSAGE_LENGTH) return null;
|
|
45
|
+
if (signerPublicKey.length !== PUBLIC_KEY_LENGTH) return null;
|
|
46
|
+
const format = getMessageFormat(message);
|
|
47
|
+
if (format === null) return null;
|
|
48
|
+
const envelope = new Uint8Array(HEADER_LENGTH + message.length);
|
|
49
|
+
let offset = 0;
|
|
50
|
+
envelope.set(SIGNING_DOMAIN, offset);
|
|
51
|
+
offset += SIGNING_DOMAIN.length;
|
|
52
|
+
envelope[offset++] = 0;
|
|
53
|
+
offset += APPLICATION_DOMAIN_LENGTH;
|
|
54
|
+
envelope[offset++] = format;
|
|
55
|
+
envelope[offset++] = 1;
|
|
56
|
+
envelope.set(signerPublicKey, offset);
|
|
57
|
+
offset += PUBLIC_KEY_LENGTH;
|
|
58
|
+
envelope[offset++] = message.length & 255;
|
|
59
|
+
envelope[offset++] = message.length >> 8;
|
|
60
|
+
envelope.set(message, offset);
|
|
61
|
+
return envelope;
|
|
83
62
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/utils/serialization.ts
|
|
65
|
+
const transactionFromBytes = (bytes) => (0, _solana_kit.getTransactionDecoder)().decode(bytes);
|
|
66
|
+
const transactionToBytes = (tx) => new Uint8Array((0, _solana_kit.getTransactionEncoder)().encode(tx));
|
|
67
|
+
/** base58 of the wire-format transaction — both legacy and v0, wire-compatible with web3.js */
|
|
68
|
+
const serializeTransaction = (tx) => _talismn_crypto.base58.encode(transactionToBytes(tx));
|
|
69
|
+
const deserializeTransaction = (transaction) => transactionFromBytes(_talismn_crypto.base58.decode(transaction));
|
|
70
|
+
/** decoded compiled message — legacy and v0 wire formats are handled transparently */
|
|
71
|
+
const getCompiledMessage = (tx) => (0, _solana_kit.getCompiledTransactionMessageDecoder)().decode(tx.messageBytes);
|
|
72
|
+
/**
|
|
73
|
+
* Whether the bytes parse as a complete compiled transaction message (legacy or v0).
|
|
74
|
+
* Wallets must refuse to sign such a payload as a "message": Solana software accounts sign raw
|
|
75
|
+
* message bytes with no domain separator, so the resulting ed25519 signature would double as a
|
|
76
|
+
* valid transaction signature.
|
|
77
|
+
*/
|
|
78
|
+
const isCompiledTransactionMessage = (bytes) => {
|
|
79
|
+
try {
|
|
80
|
+
const [, offset] = (0, _solana_kit.getCompiledTransactionMessageDecoder)().read(bytes, 0);
|
|
81
|
+
return offset === bytes.length;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
99
85
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
86
|
+
/** base64 of the compiled message bytes, the format `getFeeForMessage` expects */
|
|
87
|
+
const getMessageBase64 = (tx) => (0, _solana_kit.getBase64Decoder)().decode(tx.messageBytes);
|
|
88
|
+
const buildUnsignedTransaction = ({ feePayer, blockhash, lastValidBlockHeight, instructions, version = "legacy" }) => (0, _solana_kit.pipe)((0, _solana_kit.createTransactionMessage)({ version }), (m) => (0, _solana_kit.setTransactionMessageFeePayer)((0, _solana_kit.address)(feePayer), m), (m) => (0, _solana_kit.setTransactionMessageLifetimeUsingBlockhash)({
|
|
89
|
+
blockhash,
|
|
90
|
+
lastValidBlockHeight
|
|
91
|
+
}, m), (m) => (0, _solana_kit.appendTransactionMessageInstructions)(instructions, m), _solana_kit.compileTransaction);
|
|
92
|
+
/**
|
|
93
|
+
* Returns a copy of the transaction with its lifetime token (recent blockhash) replaced,
|
|
94
|
+
* re-encoding the compiled message. Existing signatures are reset to null — changing the
|
|
95
|
+
* blockhash invalidates them.
|
|
96
|
+
*/
|
|
97
|
+
const setTransactionBlockhash = (tx, blockhash) => {
|
|
98
|
+
const compiled = (0, _solana_kit.getCompiledTransactionMessageDecoder)().decode(tx.messageBytes);
|
|
99
|
+
const messageBytes = (0, _solana_kit.getCompiledTransactionMessageEncoder)().encode({
|
|
100
|
+
...compiled,
|
|
101
|
+
lifetimeToken: blockhash
|
|
102
|
+
});
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
messageBytes,
|
|
105
|
+
signatures: Object.freeze(Object.fromEntries(Object.keys(tx.signatures).map((address) => [address, null])))
|
|
106
|
+
});
|
|
112
107
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
const txToHumanJSON = (tx) => {
|
|
109
|
+
if (typeof tx === "string") tx = deserializeTransaction(tx);
|
|
110
|
+
const message = getCompiledMessage(tx);
|
|
111
|
+
const { header, staticAccounts } = message;
|
|
112
|
+
const isSigner = (index) => index < header.numSignerAccounts;
|
|
113
|
+
const isWritable = (index) => index < header.numSignerAccounts ? index < header.numSignerAccounts - header.numReadonlySignerAccounts : index < staticAccounts.length - header.numReadonlyNonSignerAccounts;
|
|
114
|
+
return {
|
|
115
|
+
version: message.version,
|
|
116
|
+
signatures: Object.values(tx.signatures).map((sig) => sig ? _talismn_crypto.base58.encode(sig) : null),
|
|
117
|
+
feePayer: staticAccounts[0] ?? null,
|
|
118
|
+
recentBlockhash: "lifetimeToken" in message ? message.lifetimeToken : null,
|
|
119
|
+
staticAccountKeys: staticAccounts,
|
|
120
|
+
addressTableLookups: ("addressTableLookups" in message ? message.addressTableLookups : void 0)?.map((l) => ({
|
|
121
|
+
accountKey: l.lookupTableAddress,
|
|
122
|
+
writableIndexes: Array.from(l.writableIndexes),
|
|
123
|
+
readonlyIndexes: Array.from(l.readonlyIndexes)
|
|
124
|
+
})) ?? [],
|
|
125
|
+
instructions: ("instructions" in message ? message.instructions : []).map((ix) => ({
|
|
126
|
+
programIdIndex: ix.programAddressIndex,
|
|
127
|
+
programId: staticAccounts[ix.programAddressIndex] ?? null,
|
|
128
|
+
accounts: (ix.accountIndices ?? []).map((i) => ({
|
|
129
|
+
index: i,
|
|
130
|
+
pubkey: staticAccounts[i] ?? null,
|
|
131
|
+
isSigner: isSigner(i),
|
|
132
|
+
isWritable: isWritable(i)
|
|
133
|
+
})),
|
|
134
|
+
data: _talismn_crypto.base58.encode(ix.data ?? /* @__PURE__ */ new Uint8Array())
|
|
135
|
+
}))
|
|
136
|
+
};
|
|
121
137
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/utils/signing.ts
|
|
140
|
+
/**
|
|
141
|
+
* Returns a copy of the transaction with `signature` attached for `address`.
|
|
142
|
+
* Throws if `address` is not a required signer of the transaction (the signatures
|
|
143
|
+
* map is keyed in wire order at decode time — adding a key would corrupt re-encoding).
|
|
144
|
+
*/
|
|
145
|
+
const attachTransactionSignature = (tx, address, signature) => {
|
|
146
|
+
if (!(address in tx.signatures)) throw new Error(`Address ${address} is not a signer of this transaction`);
|
|
147
|
+
return Object.freeze({
|
|
148
|
+
...tx,
|
|
149
|
+
signatures: Object.freeze({
|
|
150
|
+
...tx.signatures,
|
|
151
|
+
[address]: signature
|
|
152
|
+
})
|
|
153
|
+
});
|
|
129
154
|
};
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Returns the signature attached for `address`, verified against the transaction's message
|
|
157
|
+
* bytes; null when missing, all-zeros, or invalid. Unlike `parseTransactionInfo`, this checks
|
|
158
|
+
* a specific signer's slot, so it works on transactions with co-signers.
|
|
159
|
+
*/
|
|
160
|
+
const getVerifiedTransactionSignature = (tx, address) => {
|
|
161
|
+
const signature = tx.signatures[address];
|
|
162
|
+
return signature && _talismn_crypto.ed25519.verify(signature, tx.messageBytes, _talismn_crypto.base58.decode(address)) ? signature : null;
|
|
133
163
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
isSigner: k.isSigner,
|
|
145
|
-
isWritable: k.isWritable
|
|
146
|
-
})),
|
|
147
|
-
data: import_crypto2.base58.encode(ix.data)
|
|
148
|
-
}))
|
|
149
|
-
};
|
|
164
|
+
/**
|
|
165
|
+
* Signs the transaction's message bytes with the given ed25519 secret key and
|
|
166
|
+
* attaches the signature. Signing happens with @noble/curves (via @talismn/crypto),
|
|
167
|
+
* not WebCrypto — Ed25519 subtle crypto is too recent for the extension's support matrix.
|
|
168
|
+
*/
|
|
169
|
+
const signTransactionWithSecretKey = (tx, secretKey, expectedAddress) => {
|
|
170
|
+
const address = _talismn_crypto.base58.encode((0, _talismn_crypto.getPublicKeyFromSecret)(secretKey, "solana"));
|
|
171
|
+
if (expectedAddress && address !== expectedAddress) throw new Error("Address mismatch");
|
|
172
|
+
const signature = _talismn_crypto.ed25519.sign(tx.messageBytes, secretKey);
|
|
173
|
+
return attachTransactionSignature(tx, address, signature);
|
|
150
174
|
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
programIdIndex: ix.programIdIndex,
|
|
168
|
-
programId: staticKeys[ix.programIdIndex]?.toBase58() ?? null,
|
|
169
|
-
accounts: ix.accountKeyIndexes.map((i) => ({
|
|
170
|
-
index: i,
|
|
171
|
-
pubkey: staticKeys[i]?.toBase58() ?? null
|
|
172
|
-
})),
|
|
173
|
-
data: import_crypto2.base58.encode(ix.data)
|
|
174
|
-
}))
|
|
175
|
-
};
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/utils/transaction.ts
|
|
177
|
+
const parseTransactionInfo = (tx) => {
|
|
178
|
+
const message = getCompiledMessage(tx);
|
|
179
|
+
const signerAddresses = Object.keys(tx.signatures);
|
|
180
|
+
const feePayer = message.staticAccounts[0];
|
|
181
|
+
const address = message.version === "legacy" ? signerAddresses[0] : signerAddresses.length === 1 ? signerAddresses[0] : void 0;
|
|
182
|
+
const sigBytes = getVerifiedTransactionSignature(tx, feePayer);
|
|
183
|
+
return {
|
|
184
|
+
version: message.version,
|
|
185
|
+
recentBlockhash: "lifetimeToken" in message ? message.lifetimeToken : "",
|
|
186
|
+
feePayer,
|
|
187
|
+
signerAddresses,
|
|
188
|
+
address,
|
|
189
|
+
signature: sigBytes ? _talismn_crypto.base58.encode(sigBytes) : null
|
|
190
|
+
};
|
|
176
191
|
};
|
|
192
|
+
//#endregion
|
|
193
|
+
exports.SOLANA_CHAINS = SOLANA_CHAINS;
|
|
194
|
+
exports.attachTransactionSignature = attachTransactionSignature;
|
|
195
|
+
exports.buildUnsignedTransaction = buildUnsignedTransaction;
|
|
196
|
+
exports.deserializeTransaction = deserializeTransaction;
|
|
197
|
+
exports.getCompiledMessage = getCompiledMessage;
|
|
198
|
+
exports.getMessageBase64 = getMessageBase64;
|
|
199
|
+
exports.getSolNetworkId = getSolNetworkId;
|
|
200
|
+
exports.getVerifiedTransactionSignature = getVerifiedTransactionSignature;
|
|
201
|
+
exports.isCompiledTransactionMessage = isCompiledTransactionMessage;
|
|
202
|
+
exports.parseTransactionInfo = parseTransactionInfo;
|
|
203
|
+
exports.serializeOffchainMessage = serializeOffchainMessage;
|
|
204
|
+
exports.serializeTransaction = serializeTransaction;
|
|
205
|
+
exports.setTransactionBlockhash = setTransactionBlockhash;
|
|
206
|
+
exports.signTransactionWithSecretKey = signTransactionWithSecretKey;
|
|
207
|
+
exports.transactionFromBytes = transactionFromBytes;
|
|
208
|
+
exports.transactionToBytes = transactionToBytes;
|
|
209
|
+
exports.txToHumanJSON = txToHumanJSON;
|
|
177
210
|
|
|
178
|
-
// src/utils/signing.ts
|
|
179
|
-
var import_web32 = require("@solana/web3.js");
|
|
180
|
-
var import_crypto3 = require("@talismn/crypto");
|
|
181
|
-
var getKeypair = (secretKey) => {
|
|
182
|
-
const publicKey = (0, import_crypto3.getPublicKeyFromSecret)(secretKey, "solana");
|
|
183
|
-
const fullScretKey = new Uint8Array([...secretKey, ...publicKey]);
|
|
184
|
-
return import_web32.Keypair.fromSecretKey(fullScretKey);
|
|
185
|
-
};
|
|
186
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
187
|
-
0 && (module.exports = {
|
|
188
|
-
SOLANA_CHAINS,
|
|
189
|
-
deserializeTransaction,
|
|
190
|
-
getKeypair,
|
|
191
|
-
getSolNetworkId,
|
|
192
|
-
isVersionedTransaction,
|
|
193
|
-
parseTransactionInfo,
|
|
194
|
-
serializeTransaction,
|
|
195
|
-
solInstructionFromJson,
|
|
196
|
-
solInstructionToJson,
|
|
197
|
-
txToHumanJSON
|
|
198
|
-
});
|
|
199
211
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/utils/chains.ts","../src/utils/serialization.ts","../src/utils/transaction.ts","../src/utils/signing.ts"],"sourcesContent":["export * from \"./utils/chains\"\nexport * from \"./utils/serialization\"\nexport * from \"./utils/signing\"\nexport * from \"./utils/transaction\"\n","export const SOLANA_CHAINS = [\n \"solana:mainnet\",\n \"solana:devnet\",\n \"solana:testnet\",\n \"solana:localnet\",\n] as const\n\nexport type SolanaChainId = (typeof SOLANA_CHAINS)[number]\n\nexport const getSolNetworkId = (chain: SolanaChainId) => {\n switch (chain) {\n case \"solana:mainnet\":\n return \"solana-mainnet\"\n case \"solana:devnet\":\n return \"solana-devnet\"\n case \"solana:testnet\":\n return \"solana-testnet\"\n case \"solana:localnet\":\n return \"solana-localnet\"\n default:\n throw new Error(`Unknown Solana chain: ${chain}`)\n }\n}\n","import {\n PublicKey,\n Transaction,\n type TransactionInstruction,\n VersionedTransaction,\n} from \"@solana/web3.js\"\nimport { base58 } from \"@talismn/crypto\"\n\nimport { isVersionedTransaction } from \"./transaction\"\n\n// Serialize TransactionInstruction to JSON\nexport const solInstructionToJson = (instruction: TransactionInstruction) => {\n return {\n type: \"solana-instruction\" as const,\n value: {\n programId: instruction.programId.toString(),\n keys: instruction.keys.map((key) => ({\n pubkey: key.pubkey.toString(),\n isSigner: key.isSigner,\n isWritable: key.isWritable,\n })),\n data: instruction.data.toString(\"base64\"),\n },\n }\n}\n\nexport type SolInstructionJson = ReturnType<typeof solInstructionToJson>\n\n// Deserialize JSON back to TransactionInstruction\nexport const solInstructionFromJson = (serialized: SolInstructionJson): TransactionInstruction => {\n if (serialized.type !== \"solana-instruction\")\n throw new Error(\"Invalid serialized instruction type\")\n\n return {\n programId: new PublicKey(serialized.value.programId),\n keys: serialized.value.keys.map((key) => ({\n pubkey: new PublicKey(key.pubkey),\n isSigner: key.isSigner,\n isWritable: key.isWritable,\n })),\n data: Buffer.from(serialized.value.data, \"base64\"),\n }\n}\n\nexport const serializeTransaction = (transaction: Transaction | VersionedTransaction): string => {\n if (isVersionedTransaction(transaction)) {\n return base58.encode(transaction.serialize())\n } else {\n return base58.encode(\n transaction.serialize({ requireAllSignatures: false, verifySignatures: false })\n )\n }\n}\n\nexport const deserializeTransaction = (transaction: string): Transaction | VersionedTransaction => {\n const bytes = base58.decode(transaction)\n\n try {\n return VersionedTransaction.deserialize(bytes)\n } catch {\n return Transaction.from(bytes)\n }\n}\n\nexport const txToHumanJSON = (tx: string | Transaction | VersionedTransaction) => {\n if (typeof tx === \"string\") tx = deserializeTransaction(tx)\n return isVersionedTransaction(tx) ? versionedTxToJSON(tx) : legacyTxToJSON(tx)\n}\n\nconst legacyTxToJSON = (tx: Transaction) => {\n return {\n type: \"legacy\",\n signatures: tx.signatures.map((s) => (s.signature ? base58.encode(s.signature) : null)),\n feePayer: tx.feePayer?.toBase58() ?? null,\n recentBlockhash: tx.recentBlockhash ?? null,\n instructions: tx.instructions.map((ix) => ({\n programId: ix.programId.toBase58(),\n accounts: ix.keys.map((k) => ({\n pubkey: k.pubkey.toBase58(),\n isSigner: k.isSigner,\n isWritable: k.isWritable,\n })),\n data: base58.encode(ix.data),\n })),\n }\n}\n\nconst versionedTxToJSON = (tx: VersionedTransaction) => {\n const msg = tx.message\n\n // ⚠️ NOTE: without address lookup table accounts we only have static keys.\n const staticKeys = msg.staticAccountKeys\n\n return {\n type: \"versioned\",\n version: msg.version, // usually 0\n signatures: tx.signatures.map((sig) => base58.encode(sig)),\n recentBlockhash: msg.recentBlockhash,\n staticAccountKeys: staticKeys.map((k) => k.toBase58()),\n addressTableLookups:\n msg.addressTableLookups?.map((l) => ({\n accountKey: l.accountKey.toBase58(),\n writableIndexes: Array.from(l.writableIndexes),\n readonlyIndexes: Array.from(l.readonlyIndexes),\n })) ?? [],\n instructions: msg.compiledInstructions.map((ix) => ({\n programIdIndex: ix.programIdIndex,\n programId: staticKeys[ix.programIdIndex]?.toBase58() ?? null,\n accounts: ix.accountKeyIndexes.map((i) => ({\n index: i,\n pubkey: staticKeys[i]?.toBase58() ?? null,\n })),\n data: base58.encode(ix.data),\n })),\n }\n}\n","import type { Transaction, VersionedTransaction } from \"@solana/web3.js\"\nimport { base58, ed25519 } from \"@talismn/crypto\"\n\nexport const isVersionedTransaction = (\n transaction: Transaction | VersionedTransaction\n): transaction is VersionedTransaction => {\n return \"version\" in transaction\n}\n\nexport const parseTransactionInfo = (tx: Transaction | VersionedTransaction) => {\n if (isVersionedTransaction(tx)) {\n const recentBlockhash = tx.message.recentBlockhash\n const requiredSigners = tx.message.staticAccountKeys.filter((_, index) =>\n tx.message.isAccountSigner(index)\n )\n const address = requiredSigners.length === 1 ? requiredSigners[0].toBase58() : undefined\n const sigBytes = tx.signatures.length ? tx.signatures[0] : null\n\n // signature might be an array of zeros, signature needs to be verified manually\n const signature =\n sigBytes &&\n address &&\n ed25519.verify(sigBytes, tx.message.serialize(), base58.decode(address))\n ? base58.encode(sigBytes)\n : null\n\n return { recentBlockhash, address, signature }\n } else {\n const recentBlockhash = tx.recentBlockhash\n const address = tx.feePayer ? tx.feePayer.toBase58() : undefined\n const signature = tx.verifySignatures() ? base58.encode(tx.signature!) : null\n\n return { recentBlockhash, address, signature }\n }\n}\n","import { Keypair } from \"@solana/web3.js\"\nimport { getPublicKeyFromSecret } from \"@talismn/crypto\"\n\nexport const getKeypair = (secretKey: Uint8Array): Keypair => {\n const publicKey = getPublicKeyFromSecret(secretKey, \"solana\")\n const fullScretKey = new Uint8Array([...secretKey, ...publicKey])\n return Keypair.fromSecretKey(fullScretKey)\n}\n"],"mappings":"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","names":["import_crypto","import_web3","import_crypto"]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["base58","compileTransaction","ed25519","base58","base58"],"sources":["../src/utils/chains.ts","../src/utils/offchainMessage.ts","../src/utils/serialization.ts","../src/utils/signing.ts","../src/utils/transaction.ts"],"sourcesContent":["export const SOLANA_CHAINS = [\n \"solana:mainnet\",\n \"solana:devnet\",\n \"solana:testnet\",\n \"solana:localnet\",\n] as const\n\nexport type SolanaChainId = (typeof SOLANA_CHAINS)[number]\n\nexport const getSolNetworkId = (chain: SolanaChainId) => {\n switch (chain) {\n case \"solana:mainnet\":\n return \"solana-mainnet\"\n case \"solana:devnet\":\n return \"solana-devnet\"\n case \"solana:testnet\":\n return \"solana-testnet\"\n case \"solana:localnet\":\n return \"solana-localnet\"\n default:\n throw new Error(`Unknown Solana chain: ${chain}`)\n }\n}\n","// Solana off-chain message envelope (v0), as specified by Anza and enforced by the Ledger app\n// (1.8.0+): https://github.com/anza-xyz/agave/blob/master/docs/src/proposals/off-chain-message-signing.md\n//\n// signing domain \"\\xffsolana offchain\" (16 bytes) || header version (1 byte, 0) ||\n// application domain (32 bytes, zeros = not provided) || message format (1 byte) ||\n// signer count (1 byte) || signers (32 bytes each) || message length (2 bytes LE) || message\n//\n// Hardware wallets refuse to sign arbitrary raw bytes - only transactions and messages wrapped\n// in this envelope, and the derivation path's public key must appear in the signers list.\n// Note this is NOT the format implemented by @solana/offchain-messages (kit), whose codec\n// details differ from what the Ledger app validates.\n\nconst SIGNING_DOMAIN = new Uint8Array([\n 0xff,\n ...Array.from(\"solana offchain\", (c) => c.charCodeAt(0)),\n])\n\nconst APPLICATION_DOMAIN_LENGTH = 32\nconst PUBLIC_KEY_LENGTH = 32\n\n// domain + version + application domain + format + signer count + one signer + length\nconst HEADER_LENGTH =\n SIGNING_DOMAIN.length + 1 + APPLICATION_DOMAIN_LENGTH + 1 + 1 + PUBLIC_KEY_LENGTH + 2\n\n/** Hardware wallet compatible v0 messages are capped at 1232 bytes total, header included */\nconst MAX_MESSAGE_LENGTH = 1232 - HEADER_LENGTH\n\nconst FORMAT_RESTRICTED_ASCII = 0\nconst FORMAT_LIMITED_UTF8 = 1\n\nconst getMessageFormat = (message: Uint8Array): number | null => {\n if (message.every((byte) => byte >= 0x20 && byte <= 0x7e)) return FORMAT_RESTRICTED_ASCII\n\n try {\n new TextDecoder(\"utf-8\", { fatal: true }).decode(message)\n return FORMAT_LIMITED_UTF8\n } catch {\n return null // binary content cannot be wrapped\n }\n}\n\n/**\n * Wraps a raw message in the off-chain message envelope that hardware wallets sign.\n * Returns `null` if the message cannot be wrapped (binary content, empty, or too long).\n */\nexport const serializeOffchainMessage = (\n message: Uint8Array,\n signerPublicKey: Uint8Array\n): Uint8Array | null => {\n if (message.length === 0 || message.length > MAX_MESSAGE_LENGTH) return null\n if (signerPublicKey.length !== PUBLIC_KEY_LENGTH) return null\n\n const format = getMessageFormat(message)\n if (format === null) return null\n\n const envelope = new Uint8Array(HEADER_LENGTH + message.length)\n let offset = 0\n\n envelope.set(SIGNING_DOMAIN, offset)\n offset += SIGNING_DOMAIN.length\n\n envelope[offset++] = 0 // header version\n\n offset += APPLICATION_DOMAIN_LENGTH // application domain: zeros = not provided\n\n envelope[offset++] = format\n envelope[offset++] = 1 // signer count\n\n envelope.set(signerPublicKey, offset)\n offset += PUBLIC_KEY_LENGTH\n\n envelope[offset++] = message.length & 0xff\n envelope[offset++] = message.length >> 8\n\n envelope.set(message, offset)\n\n return envelope\n}\n","import type {\n Blockhash,\n Instruction,\n SignaturesMap,\n TransactionMessageBytes,\n TransactionMessageBytesBase64,\n} from \"@solana/kit\"\nimport {\n appendTransactionMessageInstructions,\n compileTransaction,\n createTransactionMessage,\n getBase64Decoder,\n getCompiledTransactionMessageDecoder,\n getCompiledTransactionMessageEncoder,\n getTransactionDecoder,\n getTransactionEncoder,\n pipe,\n setTransactionMessageFeePayer,\n setTransactionMessageLifetimeUsingBlockhash,\n address as solAddress,\n} from \"@solana/kit\"\nimport { base58 } from \"@talismn/crypto\"\n\nimport type { SolTransaction } from \"./transaction\"\n\nexport const transactionFromBytes = (bytes: Uint8Array): SolTransaction =>\n getTransactionDecoder().decode(bytes)\n\nexport const transactionToBytes = (tx: SolTransaction): Uint8Array =>\n new Uint8Array(getTransactionEncoder().encode(tx))\n\n/** base58 of the wire-format transaction — both legacy and v0, wire-compatible with web3.js */\nexport const serializeTransaction = (tx: SolTransaction): string =>\n base58.encode(transactionToBytes(tx))\n\nexport const deserializeTransaction = (transaction: string): SolTransaction =>\n transactionFromBytes(base58.decode(transaction))\n\n/** decoded compiled message — legacy and v0 wire formats are handled transparently */\nexport const getCompiledMessage = (tx: SolTransaction) =>\n getCompiledTransactionMessageDecoder().decode(tx.messageBytes)\n\n/**\n * Whether the bytes parse as a complete compiled transaction message (legacy or v0).\n * Wallets must refuse to sign such a payload as a \"message\": Solana software accounts sign raw\n * message bytes with no domain separator, so the resulting ed25519 signature would double as a\n * valid transaction signature.\n */\nexport const isCompiledTransactionMessage = (bytes: Uint8Array): boolean => {\n try {\n const [, offset] = getCompiledTransactionMessageDecoder().read(bytes, 0)\n return offset === bytes.length\n } catch {\n return false\n }\n}\n\n/** base64 of the compiled message bytes, the format `getFeeForMessage` expects */\nexport const getMessageBase64 = (tx: SolTransaction): TransactionMessageBytesBase64 =>\n getBase64Decoder().decode(tx.messageBytes) as TransactionMessageBytesBase64\n\nexport const buildUnsignedTransaction = ({\n feePayer,\n blockhash,\n lastValidBlockHeight,\n instructions,\n version = \"legacy\",\n}: {\n feePayer: string\n blockhash: string\n lastValidBlockHeight: bigint\n instructions: Instruction[]\n version?: \"legacy\" | 0\n}): SolTransaction =>\n pipe(\n createTransactionMessage({ version }),\n (m) => setTransactionMessageFeePayer(solAddress(feePayer), m),\n (m) =>\n setTransactionMessageLifetimeUsingBlockhash(\n { blockhash: blockhash as Blockhash, lastValidBlockHeight },\n m\n ),\n (m) => appendTransactionMessageInstructions(instructions, m),\n compileTransaction\n )\n\n/**\n * Returns a copy of the transaction with its lifetime token (recent blockhash) replaced,\n * re-encoding the compiled message. Existing signatures are reset to null — changing the\n * blockhash invalidates them.\n */\nexport const setTransactionBlockhash = (tx: SolTransaction, blockhash: string): SolTransaction => {\n const compiled = getCompiledTransactionMessageDecoder().decode(tx.messageBytes)\n const messageBytes = getCompiledTransactionMessageEncoder().encode({\n ...compiled,\n lifetimeToken: blockhash,\n }) as TransactionMessageBytes\n\n return Object.freeze({\n messageBytes,\n signatures: Object.freeze(\n Object.fromEntries(Object.keys(tx.signatures).map((address) => [address, null]))\n ) as SignaturesMap,\n })\n}\n\nexport const txToHumanJSON = (tx: string | SolTransaction) => {\n if (typeof tx === \"string\") tx = deserializeTransaction(tx)\n const message = getCompiledMessage(tx)\n const { header, staticAccounts } = message\n\n // standard account ordering: writable signers, readonly signers, writable non-signers, readonly non-signers\n const isSigner = (index: number) => index < header.numSignerAccounts\n const isWritable = (index: number) =>\n index < header.numSignerAccounts\n ? index < header.numSignerAccounts - header.numReadonlySignerAccounts\n : index < staticAccounts.length - header.numReadonlyNonSignerAccounts\n\n return {\n version: message.version,\n signatures: Object.values(tx.signatures).map((sig) => (sig ? base58.encode(sig) : null)),\n feePayer: staticAccounts[0] ?? null,\n recentBlockhash: \"lifetimeToken\" in message ? message.lifetimeToken : null,\n staticAccountKeys: staticAccounts as readonly string[],\n // ⚠️ NOTE: without address lookup table accounts we only have static keys.\n addressTableLookups:\n (\"addressTableLookups\" in message ? message.addressTableLookups : undefined)?.map((l) => ({\n accountKey: l.lookupTableAddress as string,\n writableIndexes: Array.from(l.writableIndexes),\n readonlyIndexes: Array.from(l.readonlyIndexes),\n })) ?? [],\n instructions: (\"instructions\" in message ? message.instructions : []).map((ix) => ({\n programIdIndex: ix.programAddressIndex,\n programId: staticAccounts[ix.programAddressIndex] ?? null,\n accounts: (ix.accountIndices ?? []).map((i) => ({\n index: i,\n pubkey: staticAccounts[i] ?? null,\n isSigner: isSigner(i),\n isWritable: isWritable(i),\n })),\n data: base58.encode((ix.data as Uint8Array | undefined) ?? new Uint8Array()),\n })),\n }\n}\n","import type { SignatureBytes } from \"@solana/kit\"\nimport { base58, ed25519, getPublicKeyFromSecret } from \"@talismn/crypto\"\n\nimport type { SolTransaction } from \"./transaction\"\n\n/**\n * Returns a copy of the transaction with `signature` attached for `address`.\n * Throws if `address` is not a required signer of the transaction (the signatures\n * map is keyed in wire order at decode time — adding a key would corrupt re-encoding).\n */\nexport const attachTransactionSignature = (\n tx: SolTransaction,\n address: string,\n signature: Uint8Array\n): SolTransaction => {\n if (!(address in tx.signatures))\n throw new Error(`Address ${address} is not a signer of this transaction`)\n\n return Object.freeze({\n ...tx,\n signatures: Object.freeze({ ...tx.signatures, [address]: signature as SignatureBytes }),\n })\n}\n\n/**\n * Returns the signature attached for `address`, verified against the transaction's message\n * bytes; null when missing, all-zeros, or invalid. Unlike `parseTransactionInfo`, this checks\n * a specific signer's slot, so it works on transactions with co-signers.\n */\nexport const getVerifiedTransactionSignature = (\n tx: SolTransaction,\n address: string\n): Uint8Array | null => {\n const signature = tx.signatures[address as keyof typeof tx.signatures]\n return signature &&\n ed25519.verify(signature, tx.messageBytes as unknown as Uint8Array, base58.decode(address))\n ? signature\n : null\n}\n\n/**\n * Signs the transaction's message bytes with the given ed25519 secret key and\n * attaches the signature. Signing happens with @noble/curves (via @talismn/crypto),\n * not WebCrypto — Ed25519 subtle crypto is too recent for the extension's support matrix.\n */\nexport const signTransactionWithSecretKey = (\n tx: SolTransaction,\n secretKey: Uint8Array,\n expectedAddress?: string\n): SolTransaction => {\n const address = base58.encode(getPublicKeyFromSecret(secretKey, \"solana\"))\n\n if (expectedAddress && address !== expectedAddress) throw new Error(\"Address mismatch\")\n\n const signature = ed25519.sign(tx.messageBytes as unknown as Uint8Array, secretKey)\n\n return attachTransactionSignature(tx, address, signature)\n}\n","import type { Transaction } from \"@solana/kit\"\nimport { base58 } from \"@talismn/crypto\"\n\nimport { getCompiledMessage } from \"./serialization\"\nimport { getVerifiedTransactionSignature } from \"./signing\"\n\n/**\n * A Solana transaction: raw wire message bytes plus a signer-address → signature map.\n * Kit's decoder/encoder handles both legacy and v0 wire formats transparently.\n */\nexport type SolTransaction = Transaction\n\nexport type SolTransactionInfo = {\n version: \"legacy\" | 0 | 1\n /** compiled lifetime token — the recent blockhash (or nonce for durable-nonce transactions) */\n recentBlockhash: string\n /** first static account, the account that pays the transaction fee */\n feePayer: string\n /** all required signer addresses, in wire order */\n signerAddresses: string[]\n /**\n * The account expected to sign in wallet flows.\n * `undefined` when the transaction has several signers (e.g. sponsored/partially-signed\n * dapp transactions) — callers use this to fall back to the dapp-provided address.\n */\n address: string | undefined\n /**\n * canonical (fee payer) base58 transaction signature, verified against the message bytes;\n * null when the fee payer hasn't signed\n */\n signature: string | null\n}\n\nexport const parseTransactionInfo = (tx: SolTransaction): SolTransactionInfo => {\n const message = getCompiledMessage(tx)\n const signerAddresses = Object.keys(tx.signatures)\n const feePayer = message.staticAccounts[0] as string\n\n // Behavior preserved from the web3.js implementation: legacy transactions always\n // resolve to the fee payer, versioned ones only when there is a single signer.\n const address =\n message.version === \"legacy\"\n ? signerAddresses[0]\n : signerAddresses.length === 1\n ? signerAddresses[0]\n : undefined\n\n // the canonical transaction signature is the fee payer's — may be missing or\n // all-zeros, so always verify against the message bytes\n const sigBytes = getVerifiedTransactionSignature(tx, feePayer)\n\n return {\n version: message.version,\n recentBlockhash: \"lifetimeToken\" in message ? message.lifetimeToken : \"\",\n feePayer,\n signerAddresses,\n address,\n signature: sigBytes ? base58.encode(sigBytes) : null,\n }\n}\n"],"mappings":"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"}
|