moltspay 0.2.2 → 0.2.4
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/dist/cli.js +16 -3
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +16 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +466 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +462 -9
- package/dist/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +200 -1
- package/dist/wallet/index.d.ts +200 -1
- package/dist/wallet/index.js +352 -0
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +348 -0
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/wallet/index.js
CHANGED
|
@@ -30,13 +30,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/wallet/index.ts
|
|
31
31
|
var wallet_exports = {};
|
|
32
32
|
__export(wallet_exports, {
|
|
33
|
+
AllowanceWallet: () => AllowanceWallet,
|
|
34
|
+
PermitSigner: () => PermitSigner,
|
|
33
35
|
PermitWallet: () => PermitWallet,
|
|
34
36
|
SecureWallet: () => SecureWallet,
|
|
35
37
|
Wallet: () => Wallet,
|
|
36
38
|
createWallet: () => createWallet,
|
|
37
39
|
formatPermitRequest: () => formatPermitRequest,
|
|
40
|
+
generatePermitInstructions: () => generatePermitInstructions,
|
|
38
41
|
getWalletAddress: () => getWalletAddress,
|
|
39
42
|
loadWallet: () => loadWallet,
|
|
43
|
+
signPermit: () => signPermit,
|
|
40
44
|
walletExists: () => walletExists
|
|
41
45
|
});
|
|
42
46
|
module.exports = __toCommonJS(wallet_exports);
|
|
@@ -976,15 +980,363 @@ After signing, send { v, r, s, deadline } to the Agent.
|
|
|
976
980
|
|
|
977
981
|
\u26A0\uFE0F Note: This authorization only allows the Agent to spend up to ${amount} USDC from your wallet. Your private key is never exposed.`;
|
|
978
982
|
}
|
|
983
|
+
|
|
984
|
+
// src/wallet/signPermit.ts
|
|
985
|
+
var import_ethers4 = require("ethers");
|
|
986
|
+
async function signPermit(config, params) {
|
|
987
|
+
const chain = config.chain || "base";
|
|
988
|
+
const chainConfig = getChain(chain);
|
|
989
|
+
const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
|
|
990
|
+
if (!privateKey) {
|
|
991
|
+
throw new Error("privateKey is required");
|
|
992
|
+
}
|
|
993
|
+
const rpcUrl = config.rpcUrl || chainConfig.rpc;
|
|
994
|
+
const provider = new import_ethers4.ethers.JsonRpcProvider(rpcUrl);
|
|
995
|
+
const wallet = new import_ethers4.ethers.Wallet(privateKey, provider);
|
|
996
|
+
const usdcContract = new import_ethers4.ethers.Contract(chainConfig.usdc, ERC20_ABI, provider);
|
|
997
|
+
const nonce = Number(await usdcContract.nonces(wallet.address));
|
|
998
|
+
let deadline;
|
|
999
|
+
if (!params.deadline) {
|
|
1000
|
+
deadline = Math.floor(Date.now() / 1e3) + 30 * 60;
|
|
1001
|
+
} else if (params.deadline < 1e6) {
|
|
1002
|
+
deadline = Math.floor(Date.now() / 1e3) + params.deadline * 60;
|
|
1003
|
+
} else {
|
|
1004
|
+
deadline = params.deadline;
|
|
1005
|
+
}
|
|
1006
|
+
const value = BigInt(Math.floor(params.amount * 1e6)).toString();
|
|
1007
|
+
const domain = {
|
|
1008
|
+
name: "USD Coin",
|
|
1009
|
+
version: "2",
|
|
1010
|
+
chainId: chainConfig.chainId,
|
|
1011
|
+
verifyingContract: chainConfig.usdc
|
|
1012
|
+
};
|
|
1013
|
+
const types = {
|
|
1014
|
+
Permit: [
|
|
1015
|
+
{ name: "owner", type: "address" },
|
|
1016
|
+
{ name: "spender", type: "address" },
|
|
1017
|
+
{ name: "value", type: "uint256" },
|
|
1018
|
+
{ name: "nonce", type: "uint256" },
|
|
1019
|
+
{ name: "deadline", type: "uint256" }
|
|
1020
|
+
]
|
|
1021
|
+
};
|
|
1022
|
+
const message = {
|
|
1023
|
+
owner: wallet.address,
|
|
1024
|
+
spender: params.spender,
|
|
1025
|
+
value,
|
|
1026
|
+
nonce,
|
|
1027
|
+
deadline
|
|
1028
|
+
};
|
|
1029
|
+
const signature = await wallet.signTypedData(domain, types, message);
|
|
1030
|
+
const sig = import_ethers4.ethers.Signature.from(signature);
|
|
1031
|
+
return {
|
|
1032
|
+
owner: wallet.address,
|
|
1033
|
+
spender: params.spender,
|
|
1034
|
+
value,
|
|
1035
|
+
deadline,
|
|
1036
|
+
nonce,
|
|
1037
|
+
v: sig.v,
|
|
1038
|
+
r: sig.r,
|
|
1039
|
+
s: sig.s
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
var PermitSigner = class {
|
|
1043
|
+
config;
|
|
1044
|
+
constructor(config) {
|
|
1045
|
+
this.config = config;
|
|
1046
|
+
}
|
|
1047
|
+
async sign(params) {
|
|
1048
|
+
return signPermit(this.config, params);
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
// src/wallet/AllowanceWallet.ts
|
|
1053
|
+
var import_ethers5 = require("ethers");
|
|
1054
|
+
var PERMIT_ABI2 = [
|
|
1055
|
+
...ERC20_ABI,
|
|
1056
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1057
|
+
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
1058
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1059
|
+
"function nonces(address owner) view returns (uint256)"
|
|
1060
|
+
];
|
|
1061
|
+
var AllowanceWallet = class {
|
|
1062
|
+
chain;
|
|
1063
|
+
chainConfig;
|
|
1064
|
+
address;
|
|
1065
|
+
// Agent's address
|
|
1066
|
+
wallet;
|
|
1067
|
+
provider;
|
|
1068
|
+
usdcContract;
|
|
1069
|
+
/** Stored permits from owners */
|
|
1070
|
+
permits = /* @__PURE__ */ new Map();
|
|
1071
|
+
constructor(config) {
|
|
1072
|
+
this.chain = config.chain || "base_sepolia";
|
|
1073
|
+
this.chainConfig = getChain(this.chain);
|
|
1074
|
+
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
1075
|
+
this.provider = new import_ethers5.ethers.JsonRpcProvider(rpcUrl);
|
|
1076
|
+
this.wallet = new import_ethers5.ethers.Wallet(config.privateKey, this.provider);
|
|
1077
|
+
this.address = this.wallet.address;
|
|
1078
|
+
this.usdcContract = new import_ethers5.ethers.Contract(
|
|
1079
|
+
this.chainConfig.usdc,
|
|
1080
|
+
PERMIT_ABI2,
|
|
1081
|
+
this.wallet
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Store a Permit received from Owner
|
|
1086
|
+
* Call this when Owner sends you a signed Permit
|
|
1087
|
+
*/
|
|
1088
|
+
storePermit(permit) {
|
|
1089
|
+
const ownerLower = permit.owner.toLowerCase();
|
|
1090
|
+
this.permits.set(ownerLower, permit);
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Get stored Permit for an owner
|
|
1094
|
+
*/
|
|
1095
|
+
getPermit(owner) {
|
|
1096
|
+
return this.permits.get(owner.toLowerCase());
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Check allowance status with an owner
|
|
1100
|
+
*/
|
|
1101
|
+
async checkAllowance(owner) {
|
|
1102
|
+
const ownerAddress = import_ethers5.ethers.getAddress(owner);
|
|
1103
|
+
const [allowance, ownerBalance, agentGasBalance] = await Promise.all([
|
|
1104
|
+
this.usdcContract.allowance(ownerAddress, this.address),
|
|
1105
|
+
this.usdcContract.balanceOf(ownerAddress),
|
|
1106
|
+
this.provider.getBalance(this.address)
|
|
1107
|
+
]);
|
|
1108
|
+
const allowanceNum = Number(allowance) / 1e6;
|
|
1109
|
+
const hasGas = Number(import_ethers5.ethers.formatEther(agentGasBalance)) >= 1e-4;
|
|
1110
|
+
return {
|
|
1111
|
+
owner: ownerAddress,
|
|
1112
|
+
agent: this.address,
|
|
1113
|
+
allowance: allowanceNum.toFixed(2),
|
|
1114
|
+
ownerBalance: (Number(ownerBalance) / 1e6).toFixed(2),
|
|
1115
|
+
agentGasBalance: import_ethers5.ethers.formatEther(agentGasBalance),
|
|
1116
|
+
canSpend: allowanceNum > 0 && hasGas,
|
|
1117
|
+
chain: this.chainConfig.name
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Spend from Owner's wallet using Permit allowance
|
|
1122
|
+
*
|
|
1123
|
+
* @example
|
|
1124
|
+
* ```typescript
|
|
1125
|
+
* const agent = new AllowanceWallet({
|
|
1126
|
+
* chain: 'base',
|
|
1127
|
+
* privateKey: process.env.AGENT_KEY // Only needs gas
|
|
1128
|
+
* });
|
|
1129
|
+
*
|
|
1130
|
+
* // Owner gave us a Permit
|
|
1131
|
+
* agent.storePermit(ownerPermit);
|
|
1132
|
+
*
|
|
1133
|
+
* // Spend to pay for a service
|
|
1134
|
+
* const result = await agent.spend({
|
|
1135
|
+
* to: '0xServiceProvider...',
|
|
1136
|
+
* amount: 2.99,
|
|
1137
|
+
* });
|
|
1138
|
+
* ```
|
|
1139
|
+
*/
|
|
1140
|
+
async spend(params) {
|
|
1141
|
+
const { to, amount, permit } = params;
|
|
1142
|
+
try {
|
|
1143
|
+
const toAddress = import_ethers5.ethers.getAddress(to);
|
|
1144
|
+
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
1145
|
+
let ownerPermit = permit;
|
|
1146
|
+
let ownerAddress;
|
|
1147
|
+
if (ownerPermit) {
|
|
1148
|
+
ownerAddress = import_ethers5.ethers.getAddress(ownerPermit.owner);
|
|
1149
|
+
this.storePermit(ownerPermit);
|
|
1150
|
+
} else {
|
|
1151
|
+
for (const [owner, p] of this.permits) {
|
|
1152
|
+
const allowance = await this.usdcContract.allowance(owner, this.address);
|
|
1153
|
+
if (BigInt(allowance) >= amountWei) {
|
|
1154
|
+
ownerAddress = import_ethers5.ethers.getAddress(owner);
|
|
1155
|
+
ownerPermit = p;
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (!ownerPermit) {
|
|
1160
|
+
return {
|
|
1161
|
+
success: false,
|
|
1162
|
+
error: "No valid permit found. Ask Owner to sign a Permit first.",
|
|
1163
|
+
from: "",
|
|
1164
|
+
to: toAddress,
|
|
1165
|
+
amount
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
const currentAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
|
|
1170
|
+
if (BigInt(currentAllowance) < amountWei) {
|
|
1171
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1172
|
+
if (ownerPermit.deadline < now) {
|
|
1173
|
+
return {
|
|
1174
|
+
success: false,
|
|
1175
|
+
error: `Permit expired at ${new Date(ownerPermit.deadline * 1e3).toISOString()}. Ask Owner for a new Permit.`,
|
|
1176
|
+
from: ownerAddress,
|
|
1177
|
+
to: toAddress,
|
|
1178
|
+
amount
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
const currentNonce = await this.usdcContract.nonces(ownerAddress);
|
|
1182
|
+
if (Number(currentNonce) !== ownerPermit.nonce) {
|
|
1183
|
+
return {
|
|
1184
|
+
success: false,
|
|
1185
|
+
error: `Permit nonce mismatch (expected ${ownerPermit.nonce}, got ${currentNonce}). Owner may have used this permit or signed a new one.`,
|
|
1186
|
+
from: ownerAddress,
|
|
1187
|
+
to: toAddress,
|
|
1188
|
+
amount
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
console.log("[AllowanceWallet] Submitting permit on-chain...");
|
|
1192
|
+
const permitTx = await this.usdcContract.permit(
|
|
1193
|
+
ownerAddress,
|
|
1194
|
+
this.address,
|
|
1195
|
+
ownerPermit.value,
|
|
1196
|
+
ownerPermit.deadline,
|
|
1197
|
+
ownerPermit.v,
|
|
1198
|
+
ownerPermit.r,
|
|
1199
|
+
ownerPermit.s
|
|
1200
|
+
);
|
|
1201
|
+
await permitTx.wait();
|
|
1202
|
+
console.log("[AllowanceWallet] Permit submitted:", permitTx.hash);
|
|
1203
|
+
}
|
|
1204
|
+
console.log("[AllowanceWallet] Executing transferFrom...");
|
|
1205
|
+
const tx = await this.usdcContract.transferFrom(
|
|
1206
|
+
ownerAddress,
|
|
1207
|
+
toAddress,
|
|
1208
|
+
amountWei
|
|
1209
|
+
);
|
|
1210
|
+
const receipt = await tx.wait();
|
|
1211
|
+
const newAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
|
|
1212
|
+
return {
|
|
1213
|
+
success: true,
|
|
1214
|
+
txHash: tx.hash,
|
|
1215
|
+
from: ownerAddress,
|
|
1216
|
+
to: toAddress,
|
|
1217
|
+
amount,
|
|
1218
|
+
remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
|
|
1219
|
+
explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
|
|
1220
|
+
};
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
const message = error.message;
|
|
1223
|
+
if (message.includes("ERC20InsufficientAllowance")) {
|
|
1224
|
+
return {
|
|
1225
|
+
success: false,
|
|
1226
|
+
error: "Insufficient allowance. Ask Owner to sign a new Permit with higher amount.",
|
|
1227
|
+
from: "",
|
|
1228
|
+
to,
|
|
1229
|
+
amount
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
if (message.includes("ERC20InsufficientBalance")) {
|
|
1233
|
+
return {
|
|
1234
|
+
success: false,
|
|
1235
|
+
error: "Owner's wallet has insufficient USDC balance.",
|
|
1236
|
+
from: "",
|
|
1237
|
+
to,
|
|
1238
|
+
amount
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
success: false,
|
|
1243
|
+
error: message,
|
|
1244
|
+
from: "",
|
|
1245
|
+
to,
|
|
1246
|
+
amount
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Get Agent's gas balance (ETH)
|
|
1252
|
+
*/
|
|
1253
|
+
async getGasBalance() {
|
|
1254
|
+
const balance = await this.provider.getBalance(this.address);
|
|
1255
|
+
return import_ethers5.ethers.formatEther(balance);
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
function generatePermitInstructions(params) {
|
|
1259
|
+
const { ownerAddress, agentAddress, amount, deadlineHours = 24, chain = "base" } = params;
|
|
1260
|
+
const chainConfig = getChain(chain);
|
|
1261
|
+
const deadline = Math.floor(Date.now() / 1e3) + deadlineHours * 3600;
|
|
1262
|
+
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
1263
|
+
const eip712Domain = {
|
|
1264
|
+
name: "USD Coin",
|
|
1265
|
+
version: "2",
|
|
1266
|
+
chainId: chainConfig.chainId,
|
|
1267
|
+
verifyingContract: chainConfig.usdc
|
|
1268
|
+
};
|
|
1269
|
+
const typedData = {
|
|
1270
|
+
types: {
|
|
1271
|
+
EIP712Domain: [
|
|
1272
|
+
{ name: "name", type: "string" },
|
|
1273
|
+
{ name: "version", type: "string" },
|
|
1274
|
+
{ name: "chainId", type: "uint256" },
|
|
1275
|
+
{ name: "verifyingContract", type: "address" }
|
|
1276
|
+
],
|
|
1277
|
+
Permit: [
|
|
1278
|
+
{ name: "owner", type: "address" },
|
|
1279
|
+
{ name: "spender", type: "address" },
|
|
1280
|
+
{ name: "value", type: "uint256" },
|
|
1281
|
+
{ name: "nonce", type: "uint256" },
|
|
1282
|
+
{ name: "deadline", type: "uint256" }
|
|
1283
|
+
]
|
|
1284
|
+
},
|
|
1285
|
+
primaryType: "Permit",
|
|
1286
|
+
domain: eip712Domain,
|
|
1287
|
+
message: {
|
|
1288
|
+
owner: ownerAddress,
|
|
1289
|
+
spender: agentAddress,
|
|
1290
|
+
value,
|
|
1291
|
+
nonce: "<GET_FROM_USDC_CONTRACT>",
|
|
1292
|
+
// Owner needs to query this
|
|
1293
|
+
deadline
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
const instructions = `
|
|
1297
|
+
\u{1F510} **Grant USDC Spending Allowance to Your Agent**
|
|
1298
|
+
|
|
1299
|
+
Your Agent (${agentAddress}) is requesting permission to spend up to ${amount} USDC from your wallet.
|
|
1300
|
+
|
|
1301
|
+
**What this does:**
|
|
1302
|
+
- Allows your Agent to pay for services on your behalf
|
|
1303
|
+
- Your USDC stays in YOUR wallet until spent
|
|
1304
|
+
- Agent can only spend up to the authorized amount
|
|
1305
|
+
- Expires in ${deadlineHours} hours
|
|
1306
|
+
- You can revoke anytime by moving your USDC
|
|
1307
|
+
|
|
1308
|
+
**How to sign (MetaMask / any web3 wallet):**
|
|
1309
|
+
|
|
1310
|
+
1. Go to https://etherscan.io/address/${chainConfig.usdc}#readContract
|
|
1311
|
+
2. Query \`nonces(${ownerAddress})\` to get your current nonce
|
|
1312
|
+
3. Use eth_signTypedData_v4 with the data below (replace nonce)
|
|
1313
|
+
4. Send the signature {v, r, s, deadline, nonce} to your Agent
|
|
1314
|
+
|
|
1315
|
+
**Chain:** ${chainConfig.name}
|
|
1316
|
+
**USDC Contract:** ${chainConfig.usdc}
|
|
1317
|
+
|
|
1318
|
+
**EIP-712 Typed Data:**
|
|
1319
|
+
\`\`\`json
|
|
1320
|
+
${JSON.stringify(typedData, null, 2)}
|
|
1321
|
+
\`\`\`
|
|
1322
|
+
|
|
1323
|
+
\u26A0\uFE0F Never share your private key. This signature only authorizes spending, not wallet access.
|
|
1324
|
+
`;
|
|
1325
|
+
return { instructions, typedData, eip712Domain };
|
|
1326
|
+
}
|
|
979
1327
|
// Annotate the CommonJS export names for ESM import in node:
|
|
980
1328
|
0 && (module.exports = {
|
|
1329
|
+
AllowanceWallet,
|
|
1330
|
+
PermitSigner,
|
|
981
1331
|
PermitWallet,
|
|
982
1332
|
SecureWallet,
|
|
983
1333
|
Wallet,
|
|
984
1334
|
createWallet,
|
|
985
1335
|
formatPermitRequest,
|
|
1336
|
+
generatePermitInstructions,
|
|
986
1337
|
getWalletAddress,
|
|
987
1338
|
loadWallet,
|
|
1339
|
+
signPermit,
|
|
988
1340
|
walletExists
|
|
989
1341
|
});
|
|
990
1342
|
//# sourceMappingURL=index.js.map
|