-
Notifications
You must be signed in to change notification settings - Fork 31
TON example #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
TON example #402
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
API_PUBLIC_KEY="<Turnkey API Public Key (that starts with 02 or 03)>" | ||
API_PRIVATE_KEY="<Turnkey API Private Key>" | ||
BASE_URL="https://api.turnkey.com" | ||
ORGANIZATION_ID="<Turnkey organization ID>" | ||
TON_ADDRESS="<existing TON address in your organization>" | ||
TON_PUBLIC_KEY="<existing TON pubkey compressed matching your above address in your organization>" | ||
TON_RPC_URL="<RPC url for TON, mainnet is https://toncenter.com/api/v2/jsonRPC>" | ||
TON_API_KEY="<API Key corresponding with the above RPC>" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Example: `with-ton` | ||
|
||
This is a simple example that walks through the construction of a TON transaction and sending the funds out on mainnet | ||
|
||
## Getting started | ||
|
||
### 1/ Cloning the example | ||
|
||
Make sure you have `Node.js` installed locally; we recommend using Node v18+. | ||
|
||
```bash | ||
$ git clone https://github.com/tkhq/sdk | ||
$ cd sdk/ | ||
$ corepack enable # Install `pnpm` | ||
$ pnpm install -r # Install dependencies | ||
$ pnpm run build-all # Compile source code | ||
$ cd examples/with-ton/ | ||
``` | ||
|
||
### 2/ Setting up Turnkey | ||
|
||
The first step is to set up your Turnkey organization. By following the [Quickstart](https://docs.turnkey.com/getting-started/quickstart) guide, you should have: | ||
|
||
- A public/private API key pair for Turnkey | ||
- An organization ID | ||
|
||
Once you've gathered these values, add them to a new `.env.local` file. Notice that your private key should be securely managed and **_never_** be committed to git. | ||
|
||
```bash | ||
$ cp .env.local.example .env.local | ||
``` | ||
|
||
Now open `.env.local` and add the missing environment variables: | ||
|
||
- `API_PUBLIC_KEY` | ||
- `API_PRIVATE_KEY` | ||
- `BASE_URL` | ||
- `ORGANIZATION_ID` | ||
- `TON_ADDRESS` | ||
- `TON_PUBLIC_KEY` | ||
- `TON_RPC_URL` | ||
- `TON_API_KEY` | ||
|
||
### 3/ Running the script | ||
|
||
Note that this example is currently set up with TON mainnet. You will need a balance to run this example | ||
|
||
```bash | ||
$ pnpm start | ||
``` | ||
|
||
You should see output similar to the following: | ||
|
||
``` | ||
? Recipient address: (<recipient_ton_address>) | ||
|
||
Sending 0.015 TON to <recipient_ton_address> | ||
|
||
Transaction sent successfully | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "@turnkey/example-with-ton", | ||
"version": "0.1.0", | ||
"private": true, | ||
"scripts": { | ||
"start": "pnpm tsx src/index.ts", | ||
"clean": "rimraf ./dist ./.cache", | ||
"typecheck": "tsc --noEmit" | ||
}, | ||
"dependencies": { | ||
"@inquirer/prompts": "^2.1.0", | ||
"@noble/hashes": "1.4.0", | ||
"@ton/core": "^0.59.0", | ||
"@ton/crypto": "^3.3.0", | ||
"@ton/ton": "^15.1.0", | ||
"@turnkey/http": "workspace:*", | ||
"@turnkey/sdk-server": "workspace:*", | ||
"async-retry": "^1.3.3", | ||
"cross-fetch": "^4.0.0", | ||
"dotenv": "^16.0.3" | ||
}, | ||
"devDependencies": { | ||
"@types/async-retry": "^1.4.8", | ||
"tsx": "^3.12.7", | ||
"typescript": "^5.0.4" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import * as dotenv from "dotenv"; | ||
import * as path from "path"; | ||
import { | ||
TonClient, | ||
Address, | ||
beginCell, | ||
WalletContractV4, | ||
internal, | ||
storeMessageRelaxed, | ||
SendMode, | ||
Cell, | ||
storeMessage, | ||
external, | ||
} from "@ton/ton"; | ||
import { input } from "@inquirer/prompts"; | ||
import { Turnkey } from "@turnkey/sdk-server"; | ||
import { bytesToHex } from "@noble/hashes/utils"; | ||
|
||
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") }); | ||
|
||
async function createWalletTransferV4WithTurnkey(args: { | ||
seqno: number; | ||
sendMode: number; | ||
walletId: number; | ||
messages: any[]; | ||
turnkeyClient: Turnkey; | ||
walletAddress: string; | ||
}) { | ||
// Check number of messages | ||
if (args.messages.length > 4) { | ||
throw Error("Maximum number of messages in a single transfer is 4"); | ||
} | ||
|
||
let signingMessageBuilder = beginCell().storeUint(args.walletId, 32); | ||
|
||
if (args.seqno === 0) { | ||
for (let i = 0; i < 32; i++) { | ||
signingMessageBuilder.storeBit(1); // Initial state for uninitialized wallet | ||
} | ||
} else { | ||
signingMessageBuilder.storeUint(Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds | ||
} | ||
|
||
signingMessageBuilder.storeUint(args.seqno, 32); | ||
signingMessageBuilder.storeUint(0, 8); // Simple order | ||
for (let m of args.messages) { | ||
signingMessageBuilder.storeUint(args.sendMode, 8); | ||
signingMessageBuilder.storeRef(beginCell().store(storeMessageRelaxed(m))); | ||
} | ||
|
||
const signingMessage = signingMessageBuilder.endCell().hash(); | ||
|
||
// Sign message using Turnkey | ||
const txSignResult = await args.turnkeyClient.apiClient().signRawPayload({ | ||
signWith: args.walletAddress, | ||
payload: bytesToHex(signingMessage), | ||
encoding: "PAYLOAD_ENCODING_HEXADECIMAL", | ||
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE", | ||
}); | ||
|
||
const { r, s } = txSignResult; | ||
const signatureBytes = Buffer.from(r + s, "hex"); | ||
const body = beginCell() | ||
.storeBuffer(signatureBytes) | ||
.storeBuilder(signingMessageBuilder) | ||
.endCell(); | ||
return body; | ||
} | ||
|
||
async function externalTransaction( | ||
client: TonClient, | ||
address: Address, | ||
init: { code: Cell | null; data: Cell | null } | null, | ||
body: Cell | ||
) { | ||
// Check if the contract needs initialization (init code/data) | ||
let neededInit: { code: Cell | null; data: Cell | null } | null = null; | ||
if (init && !(await client.isContractDeployed(address))) { | ||
neededInit = init; | ||
} | ||
|
||
// Create the external message | ||
const ext = external({ | ||
to: address, | ||
init: neededInit ? { code: neededInit.code, data: neededInit.data } : null, | ||
body: body, | ||
}); | ||
|
||
// Build the final message to send | ||
const boc = beginCell().store(storeMessage(ext)).endCell().toBoc(); | ||
|
||
// Send the transaction | ||
await client.sendFile(boc); | ||
} | ||
|
||
async function main() { | ||
const organizationId = process.env.ORGANIZATION_ID!; | ||
const turnkeyClient = new Turnkey({ | ||
apiBaseUrl: process.env.BASE_URL!, | ||
apiPublicKey: process.env.API_PUBLIC_KEY!, | ||
apiPrivateKey: process.env.API_PRIVATE_KEY!, | ||
defaultOrganizationId: organizationId, | ||
}); | ||
|
||
const client = new TonClient({ | ||
endpoint: process.env.TON_RPC_URL!, | ||
apiKey: process.env.TON_API_KEY!, | ||
}); | ||
const walletAddress = process.env.TON_ADDRESS!; | ||
const walletPublicKey = process.env.TON_PUBLIC_KEY!; | ||
|
||
if (!walletAddress || !walletPublicKey) { | ||
throw new Error( | ||
"Please set your TON_ADDRESS and TON_PUBLIC_KEY in the .env.local file." | ||
); | ||
} | ||
|
||
console.log(`Using TON address: ${walletAddress}`); | ||
|
||
const tonAddress = Address.parse(walletAddress); | ||
let accountData; | ||
try { | ||
accountData = await client.getBalance(tonAddress); | ||
} catch (error) { | ||
throw new Error( | ||
`Failed to retrieve balance for address ${tonAddress}: ${error}` | ||
); | ||
} | ||
if (!accountData || BigInt(accountData) === 0n) { | ||
console.log( | ||
`Your account does not exist or has zero balance. Fund your address ${walletAddress} to proceed.` | ||
); | ||
process.exit(1); | ||
} | ||
|
||
const recipientAddress = await input({ | ||
message: "Recipient address:", | ||
default: "<recipient_ton_address>", | ||
}); | ||
|
||
console.log(`\nSending 0.015 TON to ${recipientAddress}`); | ||
|
||
const tonWallet = WalletContractV4.create({ | ||
workchain: 0, | ||
publicKey: Buffer.from(walletPublicKey, "hex"), | ||
}); | ||
|
||
const opened = client.open(tonWallet); | ||
const seqno = await opened.getSeqno(); | ||
const message = internal({ | ||
value: "0.015", | ||
to: recipientAddress, | ||
body: "Transfer body", | ||
}); | ||
|
||
const body = await createWalletTransferV4WithTurnkey({ | ||
seqno, | ||
sendMode: SendMode.PAY_GAS_SEPARATELY, | ||
walletId: tonWallet.walletId, | ||
messages: [message], | ||
turnkeyClient, | ||
walletAddress, | ||
}); | ||
|
||
// Check if the wallet is deployed, if not provide init data | ||
const init = | ||
opened.init && !(await client.isContractDeployed(tonAddress)) | ||
? opened.init | ||
: null; | ||
|
||
// Send the transaction using the external transaction logic | ||
externalTransaction(client, tonAddress, init, body); | ||
|
||
console.log("Transaction sent successfully."); | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exit(1); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "../../tsconfig.base.json", | ||
"compilerOptions": { | ||
"noEmit": true, | ||
"tsBuildInfoFile": "./.cache/.tsbuildinfo" | ||
}, | ||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.json"] | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.