@framers/agentos-ext-wallet 0.2.0 → 0.2.2
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/manifest.json +4 -4
- package/package.json +13 -10
- package/src/AgentWalletManager.ts +0 -324
- package/src/SpendingPolicyEnforcer.ts +0 -169
- package/src/cards/CardManager.ts +0 -274
- package/src/cards/LithicCardAdapter.ts +0 -159
- package/src/cards/MccCategoryMap.ts +0 -138
- package/src/cards/types.ts +0 -169
- package/src/chains/EvmWalletAdapter.ts +0 -170
- package/src/chains/SolanaWalletAdapter.ts +0 -184
- package/src/index.ts +0 -315
- package/src/tools/CardSpendingSummaryTool.ts +0 -52
- package/src/tools/CardStatusTool.ts +0 -68
- package/src/tools/CheckBalanceTool.ts +0 -145
- package/src/tools/FreezeCardTool.ts +0 -52
- package/src/tools/IssueCardTool.ts +0 -76
- package/src/tools/PayWithCardTool.ts +0 -109
- package/src/tools/SendCryptoTool.ts +0 -185
- package/src/tools/UnfreezeCardTool.ts +0 -52
- package/src/tools/WalletHistoryTool.ts +0 -113
- package/src/types.ts +0 -230
- package/test/AgentWalletManager.spec.ts +0 -301
- package/test/CardManager.spec.ts +0 -428
- package/test/LithicCardAdapter.spec.ts +0 -224
- package/test/MccCategoryMap.spec.ts +0 -153
- package/test/SpendingPolicyEnforcer.spec.ts +0 -254
- package/test/card-integration.spec.ts +0 -242
- package/test/card-tools.spec.ts +0 -285
- package/test/integration.spec.ts +0 -171
- package/test/wallet-tools.spec.ts +0 -275
- package/tsconfig.json +0 -22
package/src/cards/types.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Types for the virtual card subsystem (Lithic integration).
|
|
4
|
-
* @module wallet/cards/types
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { SpendCategory } from '../types.js';
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Card primitives
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
export type CardType = 'VIRTUAL' | 'PHYSICAL';
|
|
14
|
-
export type CardState = 'OPEN' | 'PAUSED' | 'CLOSED' | 'PENDING_FULFILLMENT';
|
|
15
|
-
export type CardNetwork = 'VISA' | 'MASTERCARD';
|
|
16
|
-
export type CardTxStatus = 'PENDING' | 'SETTLED' | 'DECLINED' | 'VOIDED';
|
|
17
|
-
export type SpendLimitDuration = 'TRANSACTION' | 'MONTHLY' | 'ANNUALLY' | 'FOREVER';
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Config (lives in WalletConfig.card)
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
export interface CardConfig {
|
|
24
|
-
enabled: boolean;
|
|
25
|
-
/** Card provider. Only 'lithic' is supported. */
|
|
26
|
-
provider: 'lithic';
|
|
27
|
-
/** Default per-card spending limit in USD. */
|
|
28
|
-
defaultSpendLimitUsd: number;
|
|
29
|
-
/** Whether physical card issuance is allowed. */
|
|
30
|
-
allowPhysical: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const DEFAULT_CARD_CONFIG: CardConfig = {
|
|
34
|
-
enabled: false,
|
|
35
|
-
provider: 'lithic',
|
|
36
|
-
defaultSpendLimitUsd: 500,
|
|
37
|
-
allowPhysical: false,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Records (DB / runtime)
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
export interface AgentCardRecord {
|
|
45
|
-
id: string;
|
|
46
|
-
agentId: string;
|
|
47
|
-
/** Lithic's card token identifier. */
|
|
48
|
-
lithicCardToken: string;
|
|
49
|
-
last4: string;
|
|
50
|
-
cardType: CardType;
|
|
51
|
-
state: CardState;
|
|
52
|
-
spendLimitUsd: number;
|
|
53
|
-
spendLimitDuration: SpendLimitDuration;
|
|
54
|
-
network: CardNetwork;
|
|
55
|
-
/** Agent-assigned label for the card. */
|
|
56
|
-
memo?: string;
|
|
57
|
-
createdAt: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface CardTransactionRecord {
|
|
61
|
-
id: string;
|
|
62
|
-
/** FK to AgentCardRecord.id */
|
|
63
|
-
cardId: string;
|
|
64
|
-
/** Lithic's transaction token. */
|
|
65
|
-
lithicTxToken: string;
|
|
66
|
-
merchantName?: string;
|
|
67
|
-
/** 4-digit Merchant Category Code. */
|
|
68
|
-
merchantMcc: string;
|
|
69
|
-
/** Resolved spend category. */
|
|
70
|
-
category: SpendCategory;
|
|
71
|
-
amountUsd: number;
|
|
72
|
-
status: CardTxStatus;
|
|
73
|
-
createdAt: number;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
// Card store interface (agnostic — callers provide SQLite/memory impl)
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
|
|
80
|
-
export interface ICardStore {
|
|
81
|
-
getCard(agentId: string): Promise<AgentCardRecord | null>;
|
|
82
|
-
saveCard(record: AgentCardRecord): Promise<void>;
|
|
83
|
-
updateCardState(agentId: string, state: CardState): Promise<void>;
|
|
84
|
-
insertTransaction(record: CardTransactionRecord): Promise<void>;
|
|
85
|
-
updateTransactionStatus(id: string, status: CardTxStatus): Promise<void>;
|
|
86
|
-
getTransactions(cardId: string, limit?: number): Promise<CardTransactionRecord[]>;
|
|
87
|
-
getTransactionsByPeriod(cardId: string, since: number): Promise<CardTransactionRecord[]>;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Lithic API types (subset we need)
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
export interface CreateCardParams {
|
|
95
|
-
type: CardType;
|
|
96
|
-
memo?: string;
|
|
97
|
-
spend_limit: number;
|
|
98
|
-
spend_limit_duration: SpendLimitDuration;
|
|
99
|
-
state?: CardState;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface UpdateCardParams {
|
|
103
|
-
state?: CardState;
|
|
104
|
-
spend_limit?: number;
|
|
105
|
-
spend_limit_duration?: SpendLimitDuration;
|
|
106
|
-
memo?: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface LithicCardResponse {
|
|
110
|
-
token: string;
|
|
111
|
-
last_four: string;
|
|
112
|
-
type: CardType;
|
|
113
|
-
state: CardState;
|
|
114
|
-
spend_limit: number;
|
|
115
|
-
spend_limit_duration: SpendLimitDuration;
|
|
116
|
-
pan?: string;
|
|
117
|
-
cvv?: string;
|
|
118
|
-
exp_month?: string;
|
|
119
|
-
exp_year?: string;
|
|
120
|
-
funding: { token: string; type: string };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface LithicTransaction {
|
|
124
|
-
token: string;
|
|
125
|
-
card_token: string;
|
|
126
|
-
amount: number;
|
|
127
|
-
merchant: {
|
|
128
|
-
descriptor: string;
|
|
129
|
-
mcc: string;
|
|
130
|
-
city?: string;
|
|
131
|
-
state?: string;
|
|
132
|
-
country?: string;
|
|
133
|
-
};
|
|
134
|
-
status: 'PENDING' | 'SETTLED' | 'DECLINED' | 'VOIDED';
|
|
135
|
-
created: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface ListTxOpts {
|
|
139
|
-
begin?: string;
|
|
140
|
-
end?: string;
|
|
141
|
-
page_size?: number;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface AuthRuleParams {
|
|
145
|
-
allowed_mcc?: string[];
|
|
146
|
-
blocked_mcc?: string[];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface ICardAdapter {
|
|
150
|
-
createCard(params: CreateCardParams): Promise<LithicCardResponse>;
|
|
151
|
-
getCard(cardToken: string): Promise<LithicCardResponse>;
|
|
152
|
-
updateCard(cardToken: string, updates: UpdateCardParams): Promise<LithicCardResponse>;
|
|
153
|
-
pauseCard(cardToken: string): Promise<void>;
|
|
154
|
-
resumeCard(cardToken: string): Promise<void>;
|
|
155
|
-
closeCard(cardToken: string): Promise<void>;
|
|
156
|
-
listTransactions(cardToken: string, opts?: ListTxOpts): Promise<LithicTransaction[]>;
|
|
157
|
-
createAuthRule(cardToken: string, rule: AuthRuleParams): Promise<void>;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Spending summary
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
export interface SpendingSummary {
|
|
165
|
-
totalUsd: number;
|
|
166
|
-
period: 'day' | 'month';
|
|
167
|
-
byCategory: Array<{ category: SpendCategory; amountUsd: number }>;
|
|
168
|
-
transactionCount: number;
|
|
169
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview EVM chain adapter for the Agent Personal Wallet.
|
|
4
|
-
*
|
|
5
|
-
* Supports Ethereum, Base, and Polygon via ethers.js v6 (dynamic import).
|
|
6
|
-
*
|
|
7
|
-
* @module wunderland/wallet/chains/EvmWalletAdapter
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ChainId, IChainWalletAdapter, WalletTxStatus } from '../types.js';
|
|
11
|
-
|
|
12
|
-
// Lazily resolved ethers module (dynamic import — not a hard dependency)
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
-
let _ethers: any = null;
|
|
15
|
-
|
|
16
|
-
async function getEthers(): Promise<any> {
|
|
17
|
-
if (!_ethers) {
|
|
18
|
-
try {
|
|
19
|
-
_ethers = await import('ethers' as string);
|
|
20
|
-
} catch {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'Missing dependency: ethers is required for EVM wallet support. '
|
|
23
|
-
+ 'Install it with: npm install ethers',
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return _ethers;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Default RPC endpoints per chain. */
|
|
31
|
-
const DEFAULT_RPC: Record<string, string> = {
|
|
32
|
-
ethereum: 'https://eth.llamarpc.com',
|
|
33
|
-
base: 'https://mainnet.base.org',
|
|
34
|
-
polygon: 'https://polygon-rpc.com',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/** Well-known ERC-20 addresses per chain. */
|
|
38
|
-
export const KNOWN_TOKENS: Record<string, Record<string, string>> = {
|
|
39
|
-
ethereum: {
|
|
40
|
-
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
41
|
-
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
42
|
-
},
|
|
43
|
-
base: {
|
|
44
|
-
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
45
|
-
USDT: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',
|
|
46
|
-
},
|
|
47
|
-
polygon: {
|
|
48
|
-
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
|
|
49
|
-
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/** Minimal ERC-20 ABI for balance + transfer. */
|
|
54
|
-
const ERC20_ABI = [
|
|
55
|
-
'function balanceOf(address owner) view returns (uint256)',
|
|
56
|
-
'function transfer(address to, uint256 amount) returns (bool)',
|
|
57
|
-
'function decimals() view returns (uint8)',
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
export interface EvmWalletAdapterOptions {
|
|
61
|
-
chain: ChainId;
|
|
62
|
-
rpcUrl?: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export class EvmWalletAdapter implements IChainWalletAdapter {
|
|
66
|
-
readonly chain: ChainId;
|
|
67
|
-
private rpcUrl: string;
|
|
68
|
-
|
|
69
|
-
constructor(opts: EvmWalletAdapterOptions) {
|
|
70
|
-
this.chain = opts.chain;
|
|
71
|
-
this.rpcUrl = opts.rpcUrl || DEFAULT_RPC[opts.chain] || DEFAULT_RPC.ethereum;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async generateKeypair(): Promise<{ publicKey: string; secretKey: Uint8Array }> {
|
|
75
|
-
const { Wallet } = await getEthers();
|
|
76
|
-
const wallet = Wallet.createRandom();
|
|
77
|
-
// ethers v6 returns hex private key — convert to Uint8Array
|
|
78
|
-
const privKeyHex = wallet.privateKey.slice(2); // remove 0x
|
|
79
|
-
const secretKey = hexToBytes(privKeyHex);
|
|
80
|
-
return {
|
|
81
|
-
publicKey: wallet.address,
|
|
82
|
-
secretKey,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async getBalance(address: string): Promise<bigint> {
|
|
87
|
-
const { JsonRpcProvider } = await getEthers();
|
|
88
|
-
const provider = new JsonRpcProvider(this.rpcUrl);
|
|
89
|
-
return await provider.getBalance(address);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async getTokenBalance(address: string, tokenAddress: string): Promise<bigint> {
|
|
93
|
-
const { JsonRpcProvider, Contract } = await getEthers();
|
|
94
|
-
const provider = new JsonRpcProvider(this.rpcUrl);
|
|
95
|
-
const contract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
96
|
-
const balance: bigint = await contract.balanceOf(address);
|
|
97
|
-
return balance;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async signTransfer(
|
|
101
|
-
secretKey: Uint8Array,
|
|
102
|
-
to: string,
|
|
103
|
-
amountRaw: bigint,
|
|
104
|
-
): Promise<Uint8Array> {
|
|
105
|
-
const { JsonRpcProvider, Wallet } = await getEthers();
|
|
106
|
-
const provider = new JsonRpcProvider(this.rpcUrl);
|
|
107
|
-
const wallet = new Wallet(bytesToHex(secretKey), provider);
|
|
108
|
-
|
|
109
|
-
const tx = await wallet.populateTransaction({
|
|
110
|
-
to,
|
|
111
|
-
value: amountRaw,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const signed = await wallet.signTransaction(tx);
|
|
115
|
-
return hexToBytes(signed.slice(2));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async signTokenTransfer(
|
|
119
|
-
secretKey: Uint8Array,
|
|
120
|
-
to: string,
|
|
121
|
-
tokenAddress: string,
|
|
122
|
-
amountRaw: bigint,
|
|
123
|
-
): Promise<Uint8Array> {
|
|
124
|
-
const { JsonRpcProvider, Wallet, Contract } = await getEthers();
|
|
125
|
-
const provider = new JsonRpcProvider(this.rpcUrl);
|
|
126
|
-
const wallet = new Wallet(bytesToHex(secretKey), provider);
|
|
127
|
-
const contract = new Contract(tokenAddress, ERC20_ABI, wallet);
|
|
128
|
-
|
|
129
|
-
const tx = await contract.transfer.populateTransaction(to, amountRaw);
|
|
130
|
-
const populated = await wallet.populateTransaction(tx);
|
|
131
|
-
const signed = await wallet.signTransaction(populated);
|
|
132
|
-
return hexToBytes(signed.slice(2));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async broadcast(signedTx: Uint8Array): Promise<string> {
|
|
136
|
-
const { JsonRpcProvider } = await getEthers();
|
|
137
|
-
const provider = new JsonRpcProvider(this.rpcUrl);
|
|
138
|
-
const txResponse = await provider.broadcastTransaction('0x' + bytesToHex(signedTx));
|
|
139
|
-
await txResponse.wait(1);
|
|
140
|
-
return txResponse.hash;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async getTransactionStatus(txHash: string): Promise<WalletTxStatus> {
|
|
144
|
-
const { JsonRpcProvider } = await getEthers();
|
|
145
|
-
const provider = new JsonRpcProvider(this.rpcUrl);
|
|
146
|
-
const receipt = await provider.getTransactionReceipt(txHash);
|
|
147
|
-
|
|
148
|
-
if (!receipt) return 'pending';
|
|
149
|
-
if (receipt.status === 1) return 'confirmed';
|
|
150
|
-
return 'failed';
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
// Hex <-> bytes helpers
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
|
|
158
|
-
function hexToBytes(hex: string): Uint8Array {
|
|
159
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
160
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
161
|
-
bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
162
|
-
}
|
|
163
|
-
return bytes;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function bytesToHex(bytes: Uint8Array): string {
|
|
167
|
-
return Array.from(bytes)
|
|
168
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
169
|
-
.join('');
|
|
170
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Solana chain adapter for the Agent Personal Wallet.
|
|
4
|
-
*
|
|
5
|
-
* Uses dynamic imports for @solana/web3.js so it remains an optional dependency.
|
|
6
|
-
* Patterns derived from the existing SolanaProvider in anchor-providers.
|
|
7
|
-
*
|
|
8
|
-
* @module wunderland/wallet/chains/SolanaWalletAdapter
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { IChainWalletAdapter, WalletTxStatus } from '../types.js';
|
|
12
|
-
|
|
13
|
-
// Lazily resolved modules
|
|
14
|
-
let _web3: typeof import('@solana/web3.js') | null = null;
|
|
15
|
-
|
|
16
|
-
async function getWeb3() {
|
|
17
|
-
if (!_web3) {
|
|
18
|
-
try {
|
|
19
|
-
_web3 = await import('@solana/web3.js');
|
|
20
|
-
} catch {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'Missing dependency: @solana/web3.js is required for Solana wallet support. '
|
|
23
|
-
+ 'Install it with: npm install @solana/web3.js',
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return _web3;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface SolanaWalletAdapterOptions {
|
|
31
|
-
/** RPC endpoint URL. Defaults to mainnet-beta. */
|
|
32
|
-
rpcUrl?: string;
|
|
33
|
-
/** Commitment level for queries. */
|
|
34
|
-
commitment?: 'processed' | 'confirmed' | 'finalized';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class SolanaWalletAdapter implements IChainWalletAdapter {
|
|
38
|
-
readonly chain = 'solana' as const;
|
|
39
|
-
private rpcUrl: string;
|
|
40
|
-
private commitment: 'processed' | 'confirmed' | 'finalized';
|
|
41
|
-
|
|
42
|
-
constructor(opts: SolanaWalletAdapterOptions = {}) {
|
|
43
|
-
this.rpcUrl = opts.rpcUrl || 'https://api.mainnet-beta.solana.com';
|
|
44
|
-
this.commitment = opts.commitment || 'confirmed';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async generateKeypair(): Promise<{ publicKey: string; secretKey: Uint8Array }> {
|
|
48
|
-
const { Keypair } = await getWeb3();
|
|
49
|
-
const kp = Keypair.generate();
|
|
50
|
-
return {
|
|
51
|
-
publicKey: kp.publicKey.toBase58(),
|
|
52
|
-
secretKey: kp.secretKey,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async getBalance(address: string): Promise<bigint> {
|
|
57
|
-
const { Connection, PublicKey } = await getWeb3();
|
|
58
|
-
const conn = new Connection(this.rpcUrl, this.commitment);
|
|
59
|
-
const lamports = await conn.getBalance(new PublicKey(address));
|
|
60
|
-
return BigInt(lamports);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async getTokenBalance(address: string, tokenMint: string): Promise<bigint> {
|
|
64
|
-
const { Connection, PublicKey } = await getWeb3();
|
|
65
|
-
const conn = new Connection(this.rpcUrl, this.commitment);
|
|
66
|
-
const ownerPk = new PublicKey(address);
|
|
67
|
-
const mintPk = new PublicKey(tokenMint);
|
|
68
|
-
|
|
69
|
-
// Find the associated token account
|
|
70
|
-
const accounts = await conn.getTokenAccountsByOwner(ownerPk, { mint: mintPk });
|
|
71
|
-
if (accounts.value.length === 0) return 0n;
|
|
72
|
-
|
|
73
|
-
// Parse account data to get amount
|
|
74
|
-
const info = await conn.getTokenAccountBalance(accounts.value[0].pubkey);
|
|
75
|
-
return BigInt(info.value.amount);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async signTransfer(
|
|
79
|
-
secretKey: Uint8Array,
|
|
80
|
-
to: string,
|
|
81
|
-
amountRaw: bigint,
|
|
82
|
-
): Promise<Uint8Array> {
|
|
83
|
-
const { Keypair, PublicKey, SystemProgram, Transaction, Connection } = await getWeb3();
|
|
84
|
-
const conn = new Connection(this.rpcUrl, this.commitment);
|
|
85
|
-
const signer = Keypair.fromSecretKey(secretKey);
|
|
86
|
-
|
|
87
|
-
const tx = new Transaction().add(
|
|
88
|
-
SystemProgram.transfer({
|
|
89
|
-
fromPubkey: signer.publicKey,
|
|
90
|
-
toPubkey: new PublicKey(to),
|
|
91
|
-
lamports: Number(amountRaw),
|
|
92
|
-
}),
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
tx.feePayer = signer.publicKey;
|
|
96
|
-
tx.recentBlockhash = (await conn.getLatestBlockhash()).blockhash;
|
|
97
|
-
tx.sign(signer);
|
|
98
|
-
|
|
99
|
-
return tx.serialize();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async signTokenTransfer(
|
|
103
|
-
secretKey: Uint8Array,
|
|
104
|
-
to: string,
|
|
105
|
-
tokenMint: string,
|
|
106
|
-
amountRaw: bigint,
|
|
107
|
-
): Promise<Uint8Array> {
|
|
108
|
-
// SPL token transfers require @solana/spl-token — use dynamic import
|
|
109
|
-
const { Keypair, PublicKey, Transaction, Connection } = await getWeb3();
|
|
110
|
-
let splToken: any;
|
|
111
|
-
try {
|
|
112
|
-
splToken = await import('@solana/spl-token' as string);
|
|
113
|
-
} catch {
|
|
114
|
-
throw new Error(
|
|
115
|
-
'Missing dependency: @solana/spl-token is required for SPL token transfers. '
|
|
116
|
-
+ 'Install it with: npm install @solana/spl-token',
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const conn = new Connection(this.rpcUrl, this.commitment);
|
|
121
|
-
const signer = Keypair.fromSecretKey(secretKey);
|
|
122
|
-
const mintPk = new PublicKey(tokenMint);
|
|
123
|
-
const destPk = new PublicKey(to);
|
|
124
|
-
|
|
125
|
-
// Get or create associated token accounts
|
|
126
|
-
const sourceAta = splToken.getAssociatedTokenAddressSync(mintPk, signer.publicKey);
|
|
127
|
-
const destAta = splToken.getAssociatedTokenAddressSync(mintPk, destPk);
|
|
128
|
-
|
|
129
|
-
const tx = new Transaction();
|
|
130
|
-
|
|
131
|
-
// Create destination ATA if it doesn't exist
|
|
132
|
-
const destAccount = await conn.getAccountInfo(destAta);
|
|
133
|
-
if (!destAccount) {
|
|
134
|
-
tx.add(
|
|
135
|
-
splToken.createAssociatedTokenAccountInstruction(
|
|
136
|
-
signer.publicKey,
|
|
137
|
-
destAta,
|
|
138
|
-
destPk,
|
|
139
|
-
mintPk,
|
|
140
|
-
),
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
tx.add(
|
|
145
|
-
splToken.createTransferInstruction(
|
|
146
|
-
sourceAta,
|
|
147
|
-
destAta,
|
|
148
|
-
signer.publicKey,
|
|
149
|
-
Number(amountRaw),
|
|
150
|
-
),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
tx.feePayer = signer.publicKey;
|
|
154
|
-
tx.recentBlockhash = (await conn.getLatestBlockhash()).blockhash;
|
|
155
|
-
tx.sign(signer);
|
|
156
|
-
|
|
157
|
-
return tx.serialize();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async broadcast(signedTx: Uint8Array): Promise<string> {
|
|
161
|
-
const { Connection } = await getWeb3();
|
|
162
|
-
const conn = new Connection(this.rpcUrl, this.commitment);
|
|
163
|
-
const sig = await conn.sendRawTransaction(signedTx, {
|
|
164
|
-
skipPreflight: false,
|
|
165
|
-
preflightCommitment: this.commitment,
|
|
166
|
-
});
|
|
167
|
-
// Wait for confirmation
|
|
168
|
-
await conn.confirmTransaction(sig, this.commitment);
|
|
169
|
-
return sig;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async getTransactionStatus(txHash: string): Promise<WalletTxStatus> {
|
|
173
|
-
const { Connection } = await getWeb3();
|
|
174
|
-
const conn = new Connection(this.rpcUrl, this.commitment);
|
|
175
|
-
const status = await conn.getSignatureStatus(txHash);
|
|
176
|
-
|
|
177
|
-
if (!status.value) return 'pending';
|
|
178
|
-
if (status.value.err) return 'failed';
|
|
179
|
-
if (status.value.confirmationStatus === 'finalized' || status.value.confirmationStatus === 'confirmed') {
|
|
180
|
-
return 'confirmed';
|
|
181
|
-
}
|
|
182
|
-
return 'pending';
|
|
183
|
-
}
|
|
184
|
-
}
|